Cell 启动可达性(INV-SERVE-REACHABLE-01)
wesgine serve 到达"可被探活"的时间是有界常数,不随租户数或数据库体积增长。
核心契约
router.Start(绑端口) ← 先
sdnotify.Ready() ← 先
ActivatePersisted(热身) ← 后,在 goroutine 里
为什么顺序承重
每一个盯着这个进程的监督者都用固定超时判死:
| 监督者 | 超时 |
|---|---|
systemd TimeoutStartSec | 120s |
| teleclaw watchdog | 5s 探针 × 3 次 |
| 负载均衡器 | 配置固定 |
它们都无法区分"正在启动"和"已经死了"。
把一个随数据增长的操作放在可达性之前 → 部署跑够久之后必然穿过某个固定阈值 → 被判死、重启、把已完成的热身全部作废。
三条派生纪律
① 热身可被取消
ActivatePersisted 的循环每轮查 ctx.Err():
func (h *Hypervisor) ActivatePersisted(ctx context.Context) error {
for _, cellID := range registered {
if ctx.Err() != nil {
return ctx.Err() // SIGTERM 后不继续 boot
}
h.activateCell(ctx, cellID)
}
return nil
}
不查就会在 SIGTERM 后继续 boot 那些 Stop 已经排空的 Cell,而每一个 boot 都要等它跑完进程才能退。
② SIGKILL 是自我强化的
TimeoutStopSec 升级成 SIGKILL → 每个打开的 Cell 留下未 checkpoint 的 WAL → 下次 boot 先付恢复 → 一轮比一轮深的重启循环。
③ 跳过热身不损失正确性
Cell 中间件对不同温度的处理:
| 温度 | 首请求行为 |
|---|---|
| Cool | 同步 WarmUp |
| Cold | 503 + Retry-After + 后台热 |
sweep 没走到的 Cell 只是慢,不是坏。
代价说清楚:这些 Cell 的 Cron/IMAP 推迟到下次热身才恢复——用可预期的延迟换掉不可预期的不可达。
与 systemd 的配合
[Service]
Type=notify
TimeoutStartSec=120
TimeoutStopSec=30
Type=notify:引擎发READY=1后 systemd 才算 activeactivating期间 Java watchdog 不判死TimeoutStartSec=120:给首次解压 runtime 留时间- 不配
WatchdogSec:心跳 goroutine 检出率几乎为零
脚本形态对比
| systemd | 脚本 (wes2.1_start.sh) | |
|---|---|---|
| 就绪判定 | READY=1 信号 | sleep 2 后看进程还在 |
| 崩溃重启 | Restart=on-failure | 无 |
| 停止超时 | TimeoutStopSec=30 | 5 秒 |
| 重复保护 | systemd 单实例 | 查端口占用 |
脚本形态下,INV-SERVE-REACHABLE-01 的 systemd 相关纪律不生效,但可达性契约本身仍成立。
反模式
| 禁止 | 正确做法 |
|---|---|
ActivatePersisted 放在 router.Start 前 | 先绑端口,后热身 |
热身循环不查 ctx.Err() | 每轮查,SIGTERM 后退出 |
用 WatchdogSec 做活性检测 | /health 端点 |
| 热身失败阻塞整个进程 | 单 Cell 失败跳过,不阻塞 |