首页/产品开源/bm2,pm2的极简替代品

写了十来年前端,近几年后端服务全部跑在 bun 上,部署一直用 pm2。

pm2 没什么大毛病,但有几件事一直硌手。

去年接触了 MoonBit,能编译出原生二进制,就想明白了一件事:进程管理器这件事,本质上就是几组系统调用——fork、wait、信号、读 /proc,为什么需要一个运行时来干?

于是有了 bm2。

这篇文章讲清楚它是什么、和 pm2 的区别在哪、什么场景该用哪个。

所有数据来自我在 WSL2(Debian 13,20 核,16GB)里的真实压测,不是跑分软件的数字。

bm2 与 pm2 的 cluster 分发架构对比

先看结论

指标bm2 0.5.0pm2 7.0.4
安装体积5.5 MB(2 个二进制)23 MB + 3036 个文件
运行时依赖无Node.js ≥ 16
空闲守护进程内存~2.6 MB~50 MB
命令响应延迟~1 ms~200-400 ms
吞吐(4 实例,20k 请求)110,551 QPS110,116 QPS(fork)/ 不可用(cluster)
bun + ESM 顶层 await原生支持fork 需 CJS wrapper,cluster 不可用
daemon 崩溃后原地收养存活实例,应用无感按 dump 重启应用(会有中断)
内置通知(10 事件 × 7 平台)✅❌ 需第三方模块(已停止维护)

吞吐三者持平(进程管理器不在数据面上),这符合架构预期。

真正的差异在别处。

bm2 是什么

一个用 MoonBit 写的 Linux 进程管理器,编译后就是两个原生二进制:bm2(CLI)和 bm2d(守护进程),加起来 5.5 MB,没有任何运行时依赖。

它管理 bun 和 Node.js 应用:

  • fork 模式:每个实例独立进程、独立端口(适合 Nginx upstream)
  • cluster 模式:所有实例 SO_REUSEPORT 绑同一端口,内核分发(不需要 Nginx 也能负载均衡)
  • 崩溃自动重启(带预算,防止无限重启)
  • 内存超限自动重启
  • 掉电安全的状态持久化(原子写 + fsync)
  • daemon 崩溃后原地收养存活实例(应用零感知)
  • 内置通知系统:10 类事件 × 7 平台(飞书/钉钉/企业微信/Slack/Discord/webhook/邮件),即时 + cron 周期两种上报

配置是一个 TOML 文件,强校验,不支持执行任何代码:

name = "api"
cwd = "/wwwroot/api"
script = "index.js"
runtime = "bun"        # 或 "node"
instances = 4          # cluster 模式,SO_REUSEPORT 单端口
port = 3020
max_memory_mb = 512    # 内存超限自动重启
max_restarts = 5       # 连崩 5 次停止自愈,标记 errored

为什么不用 pm2

1. 管理器不应该是一个运行时

pm2 是 Node.js 写的,这意味着:管理 Node 应用的前提是先装好并维护一个 Node 运行时。

守护进程 God 本身占 50MB 内存,每次命令交互是一次 RPC 往返(200-400ms)。

你的服务器上可能跑着十几个服务,为了管它们,得先养一个 50MB 的 Node 进程,还要定期 npm update 防漏洞。

bm2 编译后是两个静态二进制,守护进程 2.6MB,命令响应 1ms。

升级是一条命令(bm2 upgrade),自动换入新守护进程,应用不停。

2. cluster 分发层:pm2 是单点,bm2 是内核

pm2 的 cluster 模式:master 进程 accept 连接再分发给 worker。

master 挂了,所有 worker 丢失服务 handle,整组中断。

bm2 的 cluster 模式用 Linux SO_REUSEPORT:所有实例独立 bind 同一端口,内核按连接四元组哈希分发。

没有中间层,任何实例(包括守护进程)崩溃都不影响其余实例接收连接。

守护进程崩溃后新实例通过 pidfd(内核进程句柄)验证归属,原地收养存活进程——应用零感知。

pm2 还有一个 bm2 没有(也不需要)的坑:pm2 对 bun cluster 模式的支持靠一个 500ms 定时器猜测应用是否 online(源码注释原话:“temporary workaround”)——因为 bun 不触发 Node cluster 的 online 事件。

bm2 daemon 崩溃收养流程

3. bun + ESM:pm2 需要 wrapper,bm2 原生

befly(我的后端框架)入口是 ESM + 顶层 await:

const app = await createBefly(appConfig, menuConfig);
const server = await app.start();

pm2 fork 容器用 require() 加载入口,直接报错:require() async module is unsupported。

必须额外写一个 CJS wrapper 做动态 import(),还得手工注入端口、实例号、NODE_ENV 这些变量——漏一个就启动失败。

bm2 直接 bun index.js,ESM 顶层 await、JSON import、Bun.env 全部原生工作。

4. daemon 崩溃:pm2 重建,bm2 收养

pm2 daemon 崩溃后,重启按 dump 文件重新 spawn 所有应用(先杀掉残留再起新的)——应用会经历一次完整重启。

bm2 daemon 崩溃后,新守护进程逐实例验证 pidfd 归属:活着的进程原地收养(不中断),死去的按持久化状态标记。

状态文件每次变更原子写入(fsync + rename),掉电最坏情况是回退到上一次变更前。

这个能力是我在真实部署里反复验证过的:kill -9 daemon,4 个实例的 uptime 连续不断。

bm2 还做了什么 pm2 没做的

reusePort 探测

cluster 模式要求应用监听时开启 SO_REUSEPORT。

忘开了会怎样。

第一个实例正常 bind,后续实例静默失败,实际上只有一个实例在服务——流量容量直接打了 1/N,没有任何报错。

bm2 在启动后主动探测:向端口发连接,检查是否以 REUSEPORT 方式绑定。

未开启则整组停止,标记 reuseport_missing 错误,通知你修复。

防患于未然。

内存超限重启

max_memory_mb 配置后,bm2 每 5 秒采样 RSS,超限按异常重启处理(计入重启预算,防止内存泄漏应用无限循环重启)。

pm2 的 max_memory_restart 也可以,但轮询粒度和重启策略的可控性不如 bm2。

内置通知

bm2 内置了通知系统,不需要装任何第三方模块:

  • 10 类事件:启动/停止/崩溃/预算耗尽/内存超限/OOM 判定/端口探测/磁盘压力/启动超时/阈值告警
  • 7 个平台:飞书/钉钉/企业微信/Slack/Discord/webhook/邮件
  • 两种周期:cron 健康巡检(全量体检)+ 每日定点统计(窗口峰值/重启次数)
  • 双语:配置 lang = "zh",聊天群里直接看中文

比如让 bm2 每 5 分钟给飞书机器人发一条健康巡检,每天 8 点发一份昨日统计:

[[notify.report]]
name = "健康巡检"
cron = "*/5 * * * *"
template = "health"

[[notify.report]]
name = "每日统计"
cron = "0 8 * * *"
template = "stats"

事件日志

所有管理事件(启动/退出/重启/通知失败)写 JSONL 结构化日志,与应用 stdout/stderr 严格分离,方便 jq 查询和日志采集器采集。

诚实说:pm2 也有优势

  • 生态和社区:pm2 十年沉淀,文档、教程、Stack Overflow 答案一搜一大把。bm2 是新项目,遇到问题只能看源码或找我。
  • 跨平台:pm2 能跑 macOS 和 Windows(开发环境友好)。bm2 只支持 Linux(内核 ≥ 5.3),这是设计取舍——pidfd 和 SO_REUSEPORT 都是 Linux 内核特性。
  • pm2.io 监控面板:Web 界面看指标,功能丰富。bm2 只有 CLI 输出和 JSON(适合接自己的监控系统)。
  • 模块系统:pm2-logrotate 等模块生态。bm2 功能内置,不可扩展。

什么场景用 bm2

  • Linux 服务器上部署 bun 或 Node.js 后端服务
  • 想要轻量、快速、可靠的进程管理,不需要 Web 面板
  • 应用使用 ESM + 顶层 await(bun 现代写法)
  • 需要 daemon 崩溃后应用不中断
  • 需要内置通知(飞书/钉钉/邮件),不想装第三方
  • 愿意用一条命令升级,接受 TOML 配置

什么场景继续用 pm2

  • 需要 Windows/macOS 支持(开发环境)
  • 依赖 pm2 生态模块(pm2-logrotate、pm2-io)
  • 团队已有 pm2 运维经验和脚本
  • 需要 Web 监控面板(pm2 plus)

安装

# 安装 MoonBit 工具链(一次性)
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash

# 安装 bm2(装到 ~/.moon/bin,无需配 PATH)
moon install chensuiyi/bm2/...

# 验证
bm2 version

在你的项目目录创建 bm2.toml,然后:

bm2 start    # 注册并启动
bm2 list     # 查看状态
bm2 kill api # 停止并注销

项目地址:github.com/chenbimo/moonbit-x(bm2 是 moonbit-x 仓库中的 bm2 成员)。

有问题欢迎提 issue,我会认真看每一条。