这也太简单了吧,用Node实现服务器多进程并发
这篇文章讲的是,使用node中的cluster来实现http server的高并发,以及热更新。
单进程缺点
我们之前开发node仅是单线程,而单线程虽然简单,却有很多缺点:
- node是单进程,一旦代码中出现错误导致进程关闭,整个服务就会崩掉
- 又由于是单进程,所以处理请求能力有限,只能处理完一个,才能处理下一个,发挥不了多核CPU的优势
- 单进程无法实现热更新。当文件有修改,若想应用修改,必须重启整个服务才能做到。在重启的过程中,是无法接受任何请求的
多进程优点
- 一旦代码出现错误,顶多影响一个进程的关闭,其他进程仍然正常运行,不会导致整个服务的宕机
- 一个进程同时处理一个请求,有多个进程,就可以同时处理多个请求。提高服务的并发量
- 对于实现热更新也是很简单。当node检测到文件更新,就会依次停掉,并且启动新的进程。对于新的进程就会读取新的文件。而整个过程对于客户端是无感的,唯一的感知就是页面内容的变化了。
实现一个简单的http Server
创建一个空的文件夹,新建一个index.js文件夹
//index.js
const http = require("http");
http.createServer((req, res) => {
res.end("hello world");
}).listen(8089, () => {
console.log("listen 8089");
});
创建了一个简单的http服务,本地访问http://localhost:8089
, 页面也就显示hello world
实现多进程的Http Server
下面我们开始给简单的server加上多进程功能
//index.js
const cluster = require("cluster");
const http = require("http");
const numCPUs = require("os").cpus().length;
//判断当前进程是否是主进程
if (cluster.isMaster) {
//打印主进程的信息
console.log("master: ");
//根据CPU的数量,创建相同数量的子进程
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else {
//如果是子进程,创建一个HTTP服务器
http.createServer((req, res) => {
//响应客户端的请求,返回"hello world"字符串
res.end("hello world");
}).listen(8088, () => {
//监听8088端口,打印监听成功的信息
console.log("listen 8088");
});
}
多进程功能主要依赖cluster
这个库,通过cluster.fork()
来创建子进程。并且创建子进程的数量由CPU的核心数量来决定。代码中还有一个cluster.isMaster
的判断,如果是主进程,就走主进程的逻辑;如果是子进程,就走子进程的逻辑。主进程的逻辑,主要是创建子进程子进程的逻辑,主要是创建一个HTTP服务器
注:
- 主进程创建子进程后,子进程会执行和主进程相同的文件。代码中可以对进程作区分,让主、子进程执行不同代码
- 多个子进程可以监听相同的端口,不会造成冲突
- 其实子进程不是真正监听端口,而是主进程监听一个端口。然后有请求过来,cluster模块就将任务以轮询的方式分发给子进程。
运行这段代码看看可以看到有多个
listen 8080
的log,这说明生成了多个子进程。
这里log的数量表示CPU的数量。我电脑这么强么😏
测试一下性能
为了测试多进程和单进程的性能,http server的响应代码仅为res.end("hello world");
是远远不够的,测不出什么。我将代码做了些修改:新建一个文件single_server.js, 用作测试单进程性能
//single_server.js
const http = require("http");
function febonacci(n) {
if (n <= 2) return 1;
return febonacci(n - 1) + febonacci(n - 2);
}
http.createServer((req, res) => {
febonacci(37);
res.end("hello world");
}).listen(8088, () => {
console.log("listen 8088");
});
这里新建了一个febonacci
函数,用于占用CPU的繁忙时间。有人会说可以用setTimeout
来模拟server的繁忙,但没什么用。因为setTimeout
并不会阻塞CPU。想要测试CPU多核性能,必须阻塞CPU才行。在代码中计算斐波那契数就是个很简单的办法了。
当计算第37个斐波那契数的时候,我电脑的花销是300多毫秒
再新建一个文件muti_server.js, 用来测试多进程的性能:
const cluster = require("cluster");
const http = require("http");
const numCPUs = require("os").cpus().length;
function febonacci(n) {
if (n <= 2) return 1;
return febonacci(n - 1) + febonacci(n - 2);
}
if (cluster.isMaster) {
console.log("master: ");
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else {
http.createServer((req, res) => {
febonacci(37);
res.end("hello world");
}).listen(8088, () => {
console.log("listen 8088");
});
}
代码准备好了,启动代码就可以开始测试了这里使用ApacheBench
作为server的压力测试工具,大家不用了解怎么用,会看测试结果就行
ab -n100 -c10 localhost:8088/
测试发起100个请求,每次发送10个请求,模拟多并发
测试结果如下:
- 单进程
主要几个指标:
- Time token for tests: 整个测试所花的时间
- Requests per second: 每秒钟处理请求的个数
- Time per request: 每个请求所花的时间
- 多进程
通过对比可以看出:单进程总共所花的时间是27秒,每秒钟处理3.6个请求。多进程总共花费5.7秒,每秒钟处理17.42个请求。
性能之比,高下立判
实现热更新
主要思路:用多进程来实现热更新,是用fs来监测文件是否有变动,若有变动,就关闭旧的线程,同时创建新的线程。新的线程读取了更新后的文件,就实现了热更新。修改原先的index.js文件
const cluster = require("cluster");
const http = require("http");
const numCPUs = require("os").cpus().length;
if (cluster.isMaster) {
console.log("master: ");
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
// 热更新
require("fs").watch(".", { recursive: true }, (eventType) => {
if(eventType === 'change'){
Object.values(cluster.workers).forEach((worker) => {
console.log("worker id: ", worker.id);
worker.kill();
cluster.fork();
});
}
});
} else {
http.createServer((req, res) => {
res.end("hello world");
const { url } = req;
if (Math.random() > 0.5 > 0.5) {
throw "";
}
console.log("worker res:", worker.id);
if (url === "/") process.send("count");
}).listen(8089, () => {
console.log("listen 8089");
});
}
在原先代码中,加入了监测文件更新的功能,如果监测到文件更新,worker.kill()
将旧的进程杀死,cluster.fork()
创建新的进程。
上面提到过,通过
cluster.fork()
创建的进程会读取和主进程一样的文件,主进程是index.js, 那新创建的子进程自然也执行index.js,也就自然执行到了更新之后的代码
这是保存文件后,控制台的输出
搞定
总结:
本篇文章介绍了多进程和单进程之间的优缺点,然后实现了http server多进程,并且对多进程和单进程之间的性能做了比较。在文章的最后通过多进程是实现了代码的热更新。
本文逻辑清晰,语言生动,例子详尽,是个不可多得的好文章啊。
转载自:https://juejin.cn/post/7231012186662535225