# 能力总览

这一页按「能力」组织，讲清 bm2 能做什么、怎么做、代价是什么。

具体命令与字段分别在[命令](/guide/commands)与[配置](/guide/configuration)里。

## 进程托管

一份 `bm2.toml` 描述一个项目：单个应用，一个或多个实例，上限 `1024`。

`bm2 serve` 校验配置、登记项目、启动实例、写状态。

`bm2 dev` 是同一套监督，换成开发模式与单实例，并前台跟随日志。

每次启动都是一次完整重启，所以改任何字段都在下一次启动后生效。

代价要说清：没有零停机重载。发布与灰度交给网关和你的发布流程。

## 自愈与重启预算

异常退出后按 `restart_delay_ms` 延迟重启，spawn 失败时按次数递增退避。

连续异常重启超过 `max_restarts` 就进入 `errored` 并停止自愈，等人介入。

干净退出（退出码 `0` 且运行时长超过 `min_uptime_ms`）不消耗预算。

这样设计是为了两件事：真正的偶发故障会被自动拉回来，而稳定的错误不会被无限重试掩盖。

## 内存上限

`max_memory_mb` 是单实例的内存上限。

超过按异常重启处理，并且记一条 `memory_limit` 事件，配了通知的话会推出去。

它不是容器级隔离，超限那一刻进程已经吃掉了那么多内存。真正的硬隔离要靠 cgroup 或容器。

## 两种多实例方式

`cluster` 是默认值。所有实例共用一个端口，靠内核的 `SO_REUSEPORT` 分发，没有中间层也没有单点。

条件是应用自己开 `reusePort`。bm2 启动后实测，没生效就停下整个项目并说明原因。

`fork` 让端口从 `port` 起连续分配，分发交给网关。

取舍很直接：`cluster` 少一层、少一个要维护的 upstream，`fork` 不需要应用配合、也不占内核分发能力。

## 守护进程崩溃不打断应用

守护进程用 pidfd 钉住每一个被托管的进程。

它自己崩溃时应用继续运行，新的守护进程启动后认出这些进程、校验环境、原地接管，PID 不变。

代价是守护进程重启期间没人监督：那段时间内的崩溃不会被自动重启，直到新守护进程接手。

## 优雅停止

`bm2 stop` 先发 `SIGTERM`，超过 `stop_timeout_ms` 仍未退出就 `SIGKILL`。

停止是持久的，守护进程重启或换班都不会把它带回来。

需要连接排空的应用要自己处理 `SIGTERM`：拒绝新连接、处理完进行中的请求、再退出。

## 日志

每个实例三份日志：标准输出、标准错误、崩溃诊断。

超过 `[logs] max_size_mb` 即轮转，`keep` 决定保留几代，`0` 表示不限代数。

轮转对应用透明：应用持有的文件描述符继续有效，不会因为改名而写进一个失联的旧文件。

## 事件

两个事件流：`bm2d.events.jsonl` 记守护进程与监督，`bm2.events.jsonl` 记 CLI 连接与重试。

每行一个 JSON 对象，只有管理元数据：时间、事件名、项目、实例、PID、原因。

不含环境变量值、应用输出与通知凭据。

## 通知与上报

七个平台开箱可用：飞书、钉钉、企业微信、Slack、Discord、通用 webhook、邮件。

事件类通知经过去重、限流、队列与退避重试。

周期类报告用标准 cron 表达，模板有 `health` 与 `stats` 两种。

投递是独立任务，端点慢或挂都不占用监督心跳。

按项目配置：一个项目只上报自己的事件，只发给自己配置的目标。

## 不停机自升级

`bm2 upgrade` 查 mooncakes 上的最新版本，用 `moon install` 换入两个二进制，再让新守护进程接管。

`moon install` 失败时旧版本原样保留，不会留下半套新版本。

只换了二进制时用 `bm2 reload` 手动换班，效果相同，只是不安装。

## 安全边界

控制通道走本机 `TCP` 加共享令牌，令牌比较用常量时间，状态目录权限 `0700`。

配置是静态 TOML，逐字段校验，任何内容都不会被执行。

环境变量只传白名单加项目自己的 `.env`，不继承调用方的整套环境。

通知凭据在错误、事件与 CLI 输出里永不出现。

## 它不做什么

反向代理、TLS、域名：交给 Nginx 或 Caddy。

开机自启：交给 systemd。

热重载、灰度发布、跨机编排、远程运维：交给发布流程与编排工具。

这些边界是有意的。一个进程管理器把边界守住，才可能把托管这一件事做稳。
