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_processnet 模块实现
  • 多个工作线程可以共享同一端口
  • 内置负载均衡(轮询调度,每次选择第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选项复用端口,仅某些平台可用)
管理功能 ❌(需要编码处理故障重启,日志采集等等) ❌(需要编码处理故障重启,日志采集等等) ✅(进程守护、日志系统、部署等)
适用场景 执行命令/脚本 多核服务部署 生产环境部署,守护多实例服务
相关推荐
我是谁的程序员1 分钟前
Flutter iOS真机调试报错弹窗:不受信任的开发者
后端
蓝宝石Kaze3 分钟前
使用 Viper 读取配置文件
后端
aiopencode5 分钟前
Flutter 开发指南:安卓真机、虚拟机调试及 VS Code 开发环境搭建
后端
开心猴爷8 分钟前
M1搭建flutter环境+真机调试demo
后端
沐道PHP9 分钟前
Go Gin框架安装记录
后端
技术宝哥24 分钟前
解决 Spring Boot 启动报错:数据源配置引发的启动失败
spring boot·后端·mybatis
独立开阀者_FwtCoder28 分钟前
2025年,真心佩服的十大开源工具
前端·后端·面试
喵手29 分钟前
如何快速掌握 Java 反射之获取类的字段?
java·后端·java ee
AronTing31 分钟前
06- 服务网格实战:从 Istio 核心原理到微服务治理升级
java·后端·架构
风生水气34 分钟前
在supabase中实现关键词检索和语义检索
后端·搜索引擎