Node多进程管理

我们都知道, JavaScript 代码是运行在单个进程的单线程上。它存在以下问题:

  1. 一旦程序抛出的异常没有被捕获,将会引起整个进程的崩溃。
  2. 无法充分利用服务器的多核 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选项复用端口,仅某些平台可用)
管理功能 ❌(需要编码处理故障重启,日志采集等等) ❌(需要编码处理故障重启,日志采集等等) ✅(进程守护、日志系统、部署等)
适用场景 执行命令/脚本 多核服务部署 生产环境部署,守护多实例服务

猜你喜欢

转载自blog.csdn.net/sc35262/article/details/147097376
今日推荐