第 29 章 / 共 51 章
卷四 · 热重载深潜

代际生命周期:重载瞬间的完整时序

10 个并发请求进行中提交热重载会发生什么?本章画出从编译到旧代析构的全过程——这是第 3 章挑战题的标准答案。

本章导读

站位:把散在前文的碎片(槽位、lease、双代、回收)拼成一张时序图。读完再看 reload_matrix 测试的断言,每个"为什么"都有出处。

29.1 引入

热重载最难的部分不是"换代码",而是换的瞬间:端口还在监听吗?在途请求归谁?新旧两代怎么并存又怎么分手?答案藏在两个机制里——监听槽位(端口稳定性的来源)与 lease(请求归属的来源)。把它们按时序串起来,就是本章。

29.2 端到端时序图

时刻 控制线程 worker 线程 监听槽位/端口 ──────────────────────────────────────────────────────────────────── t0 提交意图 → 冻结快照(Revision) t1 全量候选编译(无副作用) └ 失败 → FAILED,到此为止(旧代从未受影响) t2 ServiceSwap(旧代导出快照) t3 ═══ 越过不可逆提交点 ═══ t4 新代 ServiceInit(xsSwapTake) 10 个在途请求 端口持续监听 「新代装载完毕」 仍由旧代 gen N 服务 (槽位不变) t5 槽位切指针 → 新连接进 gen N+1 旧请求继续跑 t6 旧请求逐个完成 → lease 归还 t7 监测旧代引用 …… 归零 t8 GC 线程:ServiceUnit → TCC → 驱动 → 配置(旧代安葬) 注意:t4-t6 期间新旧两代并行存在;每个请求自始至终只见到一代代码
  • t3 是分界线:此前一切可失败可取消;此后只能向前(新意图排队等待)
  • t5 的切换是原子的:一个连接要么从旧代开始要么从新代开始,不存在"半个新代"
  • 每个请求自始至终只见一代——这是双代并行的核心不变量,也是"改代码不断线"的全部秘密

29.3 监听槽位与 handoff

端口 8080 的 socket 由监听槽位(ListenerSlot)持有 槽位的 pData 指向"当前代" 换代 = 槽位内切指针(写锁内完成,微秒级)—— 端口零闪烁 端点比对:class/ip/port/tls/backlog/recv_limit 全一致 → 原地 handoff 端点有变 → 结构变更路径(ch13:明确拒绝或提示重启)

handoff 让"同一端口上的新旧两代"成为可能:监听 socket 从不关闭重开——外部视角下服务从未有一毫秒不可达。TLS 端点同理,且 ClientHello 的身份选择(SNI)被 Cookie 机制绑定到同一次 Accept,握手不会跨代。

29.4 lease 的持有者

持有者取得时机归还时机
HTTP 请求Accept 后经槽位取当前代响应完成 + body 排空(keep-alive 继续持有到连接结束或下次请求再判定)
WS / TCP 连接连接建立连接终态(Close/Abort)
UDP 数据报每报一次处理完成即还
定时器创建时绑定所属代触发或换代作废
TAKEOVER 连接接管时你最终 Close 之时(ch25 的契约)

所以"旧代什么时候死"的答案完全由 lease 决定:HTTP 换代后秒级归零(keep-alive 空闲超时配合);长连接服务可以拖很久——这正是 lifecycle 测试断言"旧 keep-alive 连接继续旧脚本、终态无超时回收"的机制。

29.5 契约

  • 归属线:请求自始至终只见一代;换代原子切换、端口不闪。
  • 并行线:双代并存期间互不影响;旧代不收新请求。
  • 回收线:旧代死亡时间 = 最后一个 lease 归还时间(GC 细节在 ch30)。

常见错误

以为旧代还能收新请求
症状
重载后观察"新旧流量各一半"之类的预期落空。
原因
槽位切指针后新连接全部进新代——旧代只出不进。想做灰度请用 vhost / 双 server,而不是指望换代比例。
把"日志里 gen 不变"当重载失败
症状
提交重载后日志没有新 gen 号,以为没生效。
原因
gen 号在 script loaded 行;若内容指纹相同(Revision 一致)或目标写错(查 result.Target 字段核对),可能根本没有换代发生。以 xsReloadQuery 的终态为准,不要靠猜日志。

编程练习

基础时序图默画

不看书画 29.2 的时序图,标出 t3(不可逆点)与 t5(切指针)的位置。

验收:能解释 t2 与 t8 为什么分别在不可逆点前后。
进阶lease 观测

用长连接(WS)+ 快速连续重载 3 次,确认一个连接从头到尾由同代服务的三种证据(行为标记、日志 gen、Swap 数据)。

验收:三份证据一致;连接结束后旧代才 finalize。
挑战端到端无感验证

参考 reload_matrix:hammer 线程全程打 /ready(每 10ms),期间换代 5 次,统计失败数。

验收:失败数为 0;日志含 5 次 finalized、0 次 force-free。

小结

要点结论
时序骨架快照→编译→Swap→不可逆点→Init→切指针→lease 归零→GC
端口稳定槽位持 socket、切 pData 指针——handoff 零闪烁
双代并行旧代只出不进;每个请求只见一代
lease 表请求/连接/数据报/定时器/接管连接各有取还时机
xrt 延伸阅读