我们都知道, JavaScript 代码是运行在单个进程的单线程上。它存在以下问题:
- 一旦程序抛出的异常没有被捕获,将会引起整个进程的崩溃。
- 无法充分利用服务器的多核 CPU 。
面对单进程单线程带来的问题,我们的经验是启动多个进程来解决。
使用 child_process 内置模块
child_process
模块允许你创建子进程来执行系统命令或 Node 脚本。
特点:
- 可以执行系统命令或者 Node 脚本。
- 提供四种创建子进程的方式:
spawn()
: 启动一个子进程执行命令exec()
: 启动一个子进程来执行命令并在缓冲区中返回结果execFile()
: 启动一个子进程来执行可执行文件fork()
: 特殊的 spawn() 用于创建 Node 子进程
使用场景:
- 执行耗时操作,避免阻塞事件循环(更建议使用
worker_threads
模块) - 执行系统命令或脚本
- 启动多个 Node 实例,如 Web 服务器
示例:
主进程的代码保存为 master.js
javascript
代码解读
复制代码
const cp = require('node:child_process') const os = require('node:os') const net = require('node:net') const workers = {} // 最大启动次数 const maxRetry = 10 // 启动首次启动与最后一次启动时间间隔 const maxRetryInterval = 60 * 1000 let restartTimeList = [] const server = net.createServer().listen(8080) function createWorker() { if (isRestartFrequently()) { throw new Error('Workers frequently restart!') } const worker = cp.fork('./worker.js') worker.on('message', (message) => { if (message.action === 'restart') { console.error(`Worker ${worker.pid} restarting...`) createWorker() } }) worker.on('exit', (code) => { console.error(`Worker ${worker.pid} Exiting with code: ${code}.`) workers[worker.pid] = null }) worker.send('server', server) workers[worker.pid] = worker console.info(`Worker ${worker.pid} started.`) } // 检查是否频繁启动 function isRestartFrequently() { const length = restartTimeList.push(Date.now()) // 保留最后10次启动记录 if (length > maxRetry) { restartTimeList = restartTimeList.slice(maxRetry * -1) } return restartTimeList.length >= maxRetry && restartTimeList[restartTimeList.length - 1] - restartTimeList[0] < maxRetryInterval } os.cpus().forEach(createWorker) // 主进程退出,退出所有子进程 process.on('exit', (code) => { console.error(`Master Exiting with code: ${code}.`) for (const pid in workers) { workers[pid].kill() } })
工作进程的代码保存为 worker.js
javascript
代码解读
复制代码
const http = require('node:http') const _ = require('lodash') let worker const server = http.createServer((req, res) => { if (_.random(0, 5) === 1) { throw new Error('abort!!!!') } res.writeHead(200, { 'Content-Type': 'application/json' }) res.end(JSON.stringify({ message: `Hello, I am worker ${process.pid}`, })) }) process.on('message', (message, tcp) => { if (message === 'server') { worker = tcp worker.on('connection', (socket) => { server.emit('connection', socket) }) } }) process.on('uncaughtException', (err) => { console.error(`Worker ${process.id} Exiting with error: ${err.message}.`) process.send({ action: 'restart', }) worker.close(() => process.exit(1)) })
使用 cluster 内置模块
基于 child_process 实现的集群管理,它允许轻松创建所有共享服务器端口的子进程。
特点:
- 基于
child_process
和net
模块实现 - 多个工作线程可以共享同一端口
- 内置负载均衡(轮询调度,每次选择第
i=(i+1) mod N
个进程来发送连接)
使用场景:
- 充分利用多核 CPU 资源
- 提供应用可用性
示例:
主进程的代码保存为 master.js
js
代码解读
复制代码
const cluster = require('node:cluster') const os = require('node:os') const workers = {} function createWorker() { const worker = cluster.fork() worker.on('message', (message) => { if (message.action === 'restart') { console.error(`Worker ${worker.process.pid} restarting...`) createWorker() } }) worker.on('exit', (code) => { console.error(`Worker ${worker.process.pid} exiting with code: ${code}.`) workers[worker.id] = null }) workers[worker.id] = worker } cluster.setupPrimary({ exec: 'worker.js', }) os.cpus().forEach(createWorker) // 主进程退出,退出所有子进程 process.on('exit', (code) => { console.error(`Master Exiting with code: ${code}.`) for (const id in workers) { workers[id].kill() } })
工作进程的代码保存为 worker.js
js
代码解读
复制代码
const http = require('node:http') const _ = require('lodash') const server = http.createServer((req, res) => { if (_.random(0, 5) === 1) { throw new Error('abort!!!!') } res.writeHead(200, { 'Content-Type': 'application/json' }) res.end(JSON.stringify({ message: `Hello, I am worker ${process.pid}`, })) }).listen(8080, () => { console.info(`Worker ${process.pid} is running at http://localhost:8080`) }) process.on('uncaughtException', (err) => { console.error(`Worker ${process.pid} Exiting with error: ${err.message}.`) process.send({ action: 'restart', }) server.close(() => process.exit(1)) })
使用 pm2 第三方模块
pm2 是一个流行的 Node 进程管理器,具备负责均衡、日志管理、监控等功能。
特点:
- 进程管理
- 负责均衡
- 日志管理
- 监控功能
- 配置文件管理多应用
使用场景:
- 生产环境部署 Node 中小型应用
- 需要高可用性和自动恢复场景
- 需要监控和管理多个 Node 应用场景
示例:
配置文件代码保存为 ecosystem.config.js
javascript
代码解读
复制代码
module.exports = { apps : [{ name: 'myApp', script: 'app.js', exec_mode: 'cluster', // 使用集群模式 max_restarts: 10, // 最大重启次数 restart_delay: 0, instances: 'max', // 根据CPU核心数量启动多个进程 }], };
应用代码保存为 app.js
javascript
代码解读
复制代码
const http = require('node:http') const _ = require('lodash') http.createServer((req, res) => { if (_.random(0, 5) === 1) { throw new Error('abort!!!!') } res.writeHead(200, { 'Content-Type': 'application/json' }) res.end(JSON.stringify({ message: `Hello, I am worker ${process.pid}`, })) }).listen(8080, () => { console.info(`Server is running at http://localhost:8080`) })
基本用法:
bash
代码解读
复制代码
# 启动应用 pm2 start app.js # 通过配置文件启动应用 pm2 start ecosystem.config.js # 显示所有进程状态 pm2 list # 删除特定进程 pm2 delete pid # 停止特定进程 pm2 stop pid # 重启特定进程 pm2 restart pid # 扩容,动态增加2个实例 pm2 scale myApp +2 # 将实例增加到2个或减少为2个 pm2 scale myApp 2 # 查看日志 pm2 logs # 监控 pm2 monit
总结
特性 | child_process | cluster | pm2 |
---|---|---|---|
是否支持多进程 | ✅ | ✅ | ✅ |
是否共享端口 | ❌(可以通过设置listen() 的reusePort 选项复用端口,仅某些平台可用) |
✅ | ✅ |
管理功能 | ❌(需要编码处理故障重启,日志采集等等) | ❌(需要编码处理故障重启,日志采集等等) | ✅(进程守护、日志系统、部署等) |
适用场景 | 执行命令/脚本 | 多核服务部署 | 生产环境部署,守护多实例服务 |