NodeJS 为何性能高
NodeJS 是基于
javascript
语言,构建在 Google V8 引擎上,基于事件驱动、非阻塞 I/O 模型。虽然是单进程单线程,但是基于事件驱动模型,nodejs 的性能非常高 (高并发)。
事件驱动模型
非阻塞式模型,当有新的请求或任务到达服务器时,服务器进行响应,利用异步处理事件的能力,程序无需等待结果返回,而是基于回调通知机制,将应答结果在随后的事件回调中返回给请求者,原本同步模式下需要等待的时间,则可以用来处理其他任务。因此高效!(比如餐馆点餐)
进程:资源分配的最小单位; 线程:运算调度的最小单位;
线程隶属于进程,一个进程可以拥有多个线程;一个线程只能隶属于一个进程。
单线程
一个进程只开辟了一个线程,js
属于单线程,通过『异步操作』缓解顺序执行时带来的线程阻塞问题。
多进程
在多核心 cpu 的服务器上,可以通过 child_process.fork
或 Cluster
来启动多个进程,提高 CPU 利用率。(⚠️ 不是为了解决高并发问题)
child_process.fork 开启多进程
// child_process.js 子进程任务
function computeSum() {
console.time("computeTime");
let sum = 0;
for (let i = 0; i < 1e10; i++) {
sum += i;
}
console.timeEnd("computeTime");
return sum;
}
// 接收:主进程下发的任务通知
process.on("message", (msg) => {
console.log(msg, "process.pid:", process.pid);
// 执行大量运算任务
let res = computeSum();
// 任务执行完毕,通知主进程!
process.send(res);
});
// fork_server.js 主进程
let http = require("http");
let { fork } = require("child_process");
let server = http.createServer((req, response) => {
if (req.url == "/compute") {
console.log("on compute");
// 开启:「大量计算任务」子进程
let childProcess = fork("./child_process.js");
// 通知:子进程开始执行任务
childProcess.send("开启一个新的子进程,用于大计算。");
// 接收:子进程反馈的执行结果消息!
childProcess.on("message", (res) => {
console.log("res:", res);
response.end(`compute result is: ${res}`);
// 结束:子进程
childProcess.kill();
});
} else {
response.end(`ok`);
}
});
server.listen(3333, () => {
console.log("server running at http://127.0.0.1:3333");
});
Cluster 开启多进程
通过 cluster.isMaster
判断是否在主进程,如果是,则根据 cpu 数量调用 cluster.fork()
方法创建多个子进程。如果不是,则代表当前处在工作(worker
)进程中,则进行具体任务处理。
let http = require("http");
let cluster = require("cluster");
let cpus = require("os").cpus();
process.title = "node-master";
// 如果是主进程,执行「创建子进程」的逻辑
if (cluster.isMaster) {
console.log(`cups ${cpus.length}`);
console.log("master");
// 根据主机cpu数量创建子进程(一般为cpu数量的一半,为了平衡性能和cpu占用率)
for (let i = 0; i < i < (Math.floor(cpus.length / 2) || 1); i++) {
let worker = cluster.fork();
console.log(`子进程worker已创建: ${worker.process.pid} forked`);
worker.send(`子进程发送消息:我是子进程${worker.process.pid}, 你好呀`);
worker.on("message", (msg) => {
console.log(
`子进程接收消息:worker_${worker.process.pid} 接收到消息,内容是“ ${msg} ”`
);
});
}
} else if (cluster.isWorker) {
// 如果是子进程,执行具体任务。如开启新的http服务
console.log(`fork worker 工作进程 ${process.pid} 已启动`);
console.log(`这是工作进程 #${cluster.worker.id}`);
process.on("message", (msg) => {
console.log(`process onmessage msg is: ${msg}`);
process.send(msg + ":叫爸爸");
});
// 工作进程(子进程)可以共享任何 TCP 连接。在本例子中,共享的是 HTTP 服务器。
http
.createServer((req, res) => {
res.writeHead(200);
res.end(`你好世界 #${JSON.stringify(cluster.worker, "\t", 2)} \n`);
})
.listen(3333);
}
// 主进程和工作进程的 listening 事件都会在工作进程调用 listen 方法的时候触发。
cluster.on("listening", (worker, address) => {
console.log(`工作进程已连接到 ${address.address}:${address.port}`);
});
// 监听子进程断开事件
// 触发 disconnect 事情的原因有很多,可以是主动调用 worker.disconnect(),也可以是工作进程退出或者被 kill 掉。
cluster.on("disconnect", (worker) => {
console.log(`工作进程 #${worker.id} 已断开连接`);
});
// exit 事件会在任何一个工作进程关闭的时候触发。
// 一般用来监测 cluster 中某一个进程是否异常退出,
// 如果退出的话使用 cluster.fork 创建新的进程,以保证有足够多的进程来处理请求。
//
// exitedAfterDisconnect 表示如果工作进程由于 .kill() 或 .disconnect() 而退出的话,值就是 true。
// exitedAfterDisconnect 表示如果是以其他方式退出的话,返回值就是 false。
// exitedAfterDisconnect 表示如果工作进程尚未退出,则为 undefined。
cluster.on("exit", (worker, code, signal) => {
if (worker.exitedAfterDisconnect === true) {
console.log(
"工作进程 %d 关闭 (%s).这是自发退出,无需担心",
worker.process.pid,
signal || code
);
} else {
console.log(
"工作进程 %d 关闭 (%s).这是异常退出,重启中...",
worker.process.pid,
signal || code
);
cluster.fork();
}
});
Cluster
开启多进程时候端口疑问讲解:
如果多个 Node 进程监听同一个端口时会出现 Error:listen EADDRIUNS
的错误,而 cluster
模块为什么可以让多个子进程监听同一个端口呢? 原因是 master
进程内部启动了一个 TCP
服务器,而真正监听端口的只有这个服务器,当来自前端的请求触发服务器的 connection
事件后,master
会将对应的 socket
具柄发送给子进程。
进程守护
PM2 管理
Docs
This is a .md using a custom component
Recommended IDE setup: VS Code + Volar
Vite Documentation | Vue 3 Documentation
Edit components/HelloWorld.vue
to test hot module replacement.
More docs
...