Node.js如何成为高并发的神器
Javascript是一种单线程的,不像Java等其他多线程可以创建多个线程并行执行。在面对高并发的场景下,Node.js那不是很脆弱么?
单线程
- 事件轮询(事件驱动)和异步I/O(非阻塞I/O)
在事件驱动模型中,每个I/O工作被添加到事件队列中,线程循环地处理队列上得工作任务,当执行过程中遇到阻塞(读取文件、查询数据库)时,线程不会停下来等待结果,而是留下一个处理结果得回调函数,转而继续执行队列中的下一个任务。这个传递到队列中的回调函数在阻塞任务运行结束后才被线程调用。 - Javascript是单线程的但是不管是浏览器还是Node.js环境都是多线程的。
Node.js在接受任务的时候是多线程的,无需切换进程/线程,非常高效,但它在执行具体任务的时候是多线程的。
Libuv由事件循环和线程池组成,负责所有的I/O任务的分发与执行。 - Nginx和Apache一样,是一个HTTP服务器,它可以完胜Apache不是用的带有阻塞的多线程方式,而是带有异步I/O的事件轮询,它因此变成了响应能力更强的解决方案。
异常处理
子进程
process
process对象是一个全局变量,它提供有关当前Node.js进程的信息并对其进行控制,无须引用,在任意位置都可以使用。
- 统计信息:CPU、内存
- 事件循环:nextTick
- 异常捕获:uncaughtException
- 其他:进程、I/O、路径
子进程的实现
主进程
const child_process = require('child_process') const child = child_process.fork('./child.js') child.on('message', function(m){ console.log('来自子进程的消息', m) }) child.send({ from: '父进程消息'})
子进程
子进程是一个独立的V8实例,可以在进程中做一些耗时操作,然后在通知主进程process.on('message', function(m){ console.log('来自父进程的消息', m) }) const exec = require('child_process').exec exec('ls process.js', function(err, stdout, stderr){ console.log(stdout) console.log(stderr) process.send({stdout:stdout, stderr: stderr }) })
集群
单个Node.js实例运行在单个线程中。为了充分利用多个系统,又是需要启动一组Node.js进程去处理负载任务。
cluster模块
Node.js提供了集群模块,简单讲就是复制一些可以共享TCP连接的工作线程。
工作进程由child_process.fork()方法创建,因此它们可以使用IPC和父进程通信,从而使各进程交替处理连接服务。
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`主进程 ${process.pid} 正在运行`);
// 衍生工作进程。
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`工作进程 ${worker.process.pid} 已退出`);
});
} else {
// 工作进程可以共享任何 TCP 连接。
// 在本例子中,共享的是 HTTP 服务器。
http.createServer((req, res) => {
res.writeHead(200);
res.end('你好世界\n');
}).listen(8000);
console.log(`工作进程 ${process.pid} 已启动`);
}
cluster的两种分发连接方法
- 循环法,由主进程负责监听端口,接收新连接后再将连接循环分发给工作进程,再分发中使用了一些内置技巧防止工作进程任务过载。
- 主进程创建监听socket后发送给感兴趣的工作进程,由工作进程负责直接接收连接。
PM2
- 集群模式启动
pm2 start app.js -i 4
-i参数告诉pm2以cluster_mode的形式运行你的app(对应的叫fork_mode),后面的数字表示要启动的工作线程的数量。如果给定的数字为0,pm2则会根据你的CPU核心的数量来生成对应的工作线程。
2. 实时扩展集群
pm2 scale app +3
需要增加工作线程的数量,可以同pm2来对集群进程扩展
多线程
thread
Node.js由于Javascript执行在单一线程,导致CPU密集计算的任务可能会使主线程处于繁忙的状态,进而影响服务的性能,虽然可以通过child_process模块创建子进程的方式来解决,但是一方面进程之间无法共享内存,另一方面创建进程的开销也不小。
const {
Worker, isMainThread, parentPort, workerData
} = require('worker_threads');
if (isMainThread) {
module.exports = function parseJSAsync(script) {
return new Promise((resolve, reject) => {
const worker = new Worker(__filename, {
workerData: script
});
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0)
reject(new Error(`Worker stopped with exit code ${code}`));
});
});
};
} else {
const { parse } = require('some-js-parsing-library');
const script = workerData;
parentPort.postMessage(parse(script));
}
真正的多线程——node-threads-a-gogo
数据库
Redis
其他数据库
I/O密集型和CPU密集型
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 2313843501@qq.com
文章标题:Node.js如何成为高并发的神器
本文作者:heshuai
发布时间:2019-10-21, 10:01:17
最后更新:2019-10-21, 15:00:34
原始链接:https://heshuai326.github.io/2019/10/21/Node-js如何成为高并发的神器/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。