likes
comments
collection
share

这也太简单了吧,用Node实现服务器多进程并发

作者站长头像
站长
· 阅读数 80

这篇文章讲的是,使用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这也太简单了吧,用Node实现服务器多进程并发

实现多进程的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模块就将任务以轮询的方式分发给子进程。

运行这段代码看看这也太简单了吧,用Node实现服务器多进程并发可以看到有多个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个请求,模拟多并发

测试结果如下:

  • 单进程

这也太简单了吧,用Node实现服务器多进程并发

主要几个指标:

  1. Time token for tests: 整个测试所花的时间
  2. Requests per second: 每秒钟处理请求的个数
  3. Time per request: 每个请求所花的时间
  • 多进程

这也太简单了吧,用Node实现服务器多进程并发通过对比可以看出:单进程总共所花的时间是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,也就自然执行到了更新之后的代码

这是保存文件后,控制台的输出这也太简单了吧,用Node实现服务器多进程并发

搞定

总结:

本篇文章介绍了多进程和单进程之间的优缺点,然后实现了http server多进程,并且对多进程和单进程之间的性能做了比较。在文章的最后通过多进程是实现了代码的热更新。

本文逻辑清晰,语言生动,例子详尽,是个不可多得的好文章啊。

转载自:https://juejin.cn/post/7231012186662535225
评论
请登录