bm2,Node.js 与 Bun 项目部署新选择

写了十来年前端了,用的一直都是 JavaScript,理所当然地,后端接口也早已习惯了用 JavaScript 实现。

用 JavaScript 写前端,一个不可避免的问题,就是要在线上部署运行。

与本地开发不同的是,线上可是正式环境,不能再用开发模式运行部署,否则终端一断开,或者用户量一大,包括各种报错导致应用崩溃就麻烦了。

社区一直以来的最佳方案就是用 pm2 部署,我也用了很多年,非常不错。

但时间久了,官方的维护力度,更新周期,都进入了老牛拉车阶段,当然,既然能用,确实也用不着频繁更新。

但从使用者来说,还是希望能时不时看到有新东西,新功能出来的,包括后面 Bun 从 Node.js 嘴里咬了一块肉,PM2 对 Bun 的支持也是二等公民。

这两年我接触,使用,推广 MoonBit编程语言,深深感受到了这门语言的魅力和优雅,与编程语言的当红辣子鸡 Rust 相比,除了生态方面目前正在追赶之外,其他方面都不遑多让。

MoonBit 不仅可以生成 exe/可执行文件,还能生成 js 和最好的 wasm,一个语言就能搞定好几个主流场景。

所以,就产生了用 MoonBit 来重写 pm2 的想法,命名为 bm2,前后大概总共消耗 50亿 token,别以为用 AI 写的项目都是玩具,光是 bm2 的配置方案,风格,就讨论了两天,并修改了十几版。

今天 bm2 正式发布 0.6.0 版本,欢迎大家使用和反馈,开源地址是:https://github.com/chenbimo/moonbit-x。

这个仓库是我所有的 MoonBit 生态项目的集合,会开发更多有用,好用的软件进行开源分享。

那么说了这么多,下面详细分析 bm2 的架构,评测,场景,用法和配置。

评测对比

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

bm2 是什么

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

它管理 bun 和 Node.js 应用:

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

配置是一个 TOML 文件,强校验,有效防止无效配置:

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 事件。

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

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

js 复制代码
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 点发一份昨日统计:

toml 复制代码
[[notify.report]]
name = "健康巡检"
cron = "*/5 * * * *"
template = "health"

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

事件日志

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

安装

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

# 安装 bm2(装到 ~/.moon/bin,无需配 PATH,后面的三个点没写错,直接复制!)
moon install chensuiyi/bm2/...

# 验证
bm2 version

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

bash 复制代码
bm2 start    # 注册并启动
bm2 list     # 查看状态
bm2 kill api # 停止并注销

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

有问题欢迎提 issue 或私聊反馈,公众号 陈随易,官网:https://yicode.tech。

相关推荐
架构技术专栏2 小时前
AI Agent 框架怎么选:从一次制度查询拆出技术边界
后端·面试
福兮说2 小时前
canvas 旋转图片的六个坑:四角被裁、Math.ceil 多出 1px 黑边、JPG 角发黑、翻转方向反了
前端·javascript·图像处理·canvas
传人once3 小时前
多图片上传预览功能如何写
前端·css·html·jquery·html5
Eric.464 小时前
Wan‑Animate+MiniMax-H3 本地部署 AI 漫剧流水线:8G 显存动作迁移与动态分镜工程实战
前端·人工智能·comfyui·ai漫剧
明月_清风4 小时前
SaaS 的三层挣钱逻辑,正在被 AI 从第一层击穿
人工智能·后端
库拉镜像AI牛牛5 小时前
短剧内容自动化生产:知漫剧工作室落地教程
大数据·服务器·前端·人工智能·语音识别
谁在黄金彼岸5 小时前
nginx服务化五套方案原理解剖与选型
后端
一千柯橘5 小时前
ReAct Agent 思维范式
程序员·ai编程
律宏阔5 小时前
Flutter 列表图片内存优化:CachedNetworkImage 与 ClipRRect 的取舍
前端·flutter