热更新体系
Category
system / 脚本与热更子树。节点定位:热更新体系——解决什么问题、一条下载链之外的能力清单、兼容性是最危险问题、回滚与灰度优先级、与测试的关系。
Definition
热更新最吸引人的地方,是它看起来能把“发现问题到修复生效”的时间压得很短。对长线运营项目来说,这种能力的确非常重要,因为很多问题根本等不起完整发版和平台审核。但热更新真正危险的地方也在这里:它缩短的是修复窗口,不是测试责任,更不是设计责任。
如果没有版本兼容、签名校验、灰度发布和快速回滚能力,热更新不会让系统更安全,只会让错误更快到达更多玩家手里。
Problem
热更最容易被当成「下载成功就完事」,实际最危险的是兼容性:校验全绿也挡不住灰度桶崩掉。回滚能力必须优先级极高,灰度不是附加优化而是事故隔离机制——没有回滚的热更等于没有刹车。
热更新真正解决什么问题
热更新最适合解决的是这些现实压力:
- 平台发版周期长
- 运营活动频繁切换
- 线上问题需要快速止损
- 某些脚本、配置、资源改动不值得整包发版
它的本质不是“让所有东西都可变”,而是给系统建立一条比整包发布更快的受控修复链路。
一套热更新体系通常不只是一条下载链
成熟的热更新体系,通常至少包含下面几层能力:
- 补丁内容组织
- 版本与依赖关系管理
- 下载、校验和签名验证
- 灰度和分批放量
- 回滚与失效策略
- 与宿主版本的兼容判断
如果这些能力没有一起建立,所谓热更新通常只是“远程替换文件”,不是真正可运营的体系。
最危险的问题是兼容性,而不是下载失败
很多团队做热更新时,最先盯的是补丁大小和下载成功率,但真正容易出大事故的往往是兼容性:
- 新脚本依赖了旧宿主没有的接口
- 新配置要求的字段旧代码不认识
- 新资源引用了未下发的依赖
- 补丁之间存在顺序依赖,却没有被正确表达
这类问题之所以危险,是因为补丁可能下载和校验都成功了,但运行时才开始崩。
回滚能力为什么必须优先级极高
没有回滚的热更新,本质上是在把线上当试验场。真正可用的热更新体系,必须回答:
- 补丁出问题时能否快速停用
- 客户端是否能退回上一稳定版本
- 回滚后状态和缓存如何处理
- 服务端是否知道客户端当前补丁层级
这些问题如果没被提前设计,事故发生时团队往往只能二次发补丁去修补丁,风险会继续叠加。
灰度不是附加优化,而是事故隔离机制
热更新之所以需要灰度,不是因为“这样更专业”,而是因为任何补丁都可能存在真实世界里的兼容差异。设备型号、网络环境、历史版本、缓存状态都会影响结果。灰度的价值就在于:
- 先缩小影响面
- 先观察错误率和兼容性
- 先验证补丁链是否真的可恢复
没有灰度,热更新的事故半径通常会远大于整包发版。
热更新和测试是什么关系
热更新能缩短修复路径,但它从来不能替代测试。更准确的说法是,热更新会让你更需要分层测试:
- 补丁内容自身测试
- 与当前宿主版本的兼容测试
- 与历史补丁链的回归测试
- 回滚路径和失效路径测试
如果团队把热更新当成“线上修就是测试”,那它最终只会变成制造更多线上状态组合的来源。
一个更稳妥的体系化做法
比较稳妥的热更新体系通常会做到:
- 补丁内容有清楚边界,知道哪些可热更,哪些必须整包
- 补丁与宿主版本之间有明确兼容矩阵
- 发布链路内建签名、灰度、观测和回滚
- 服务端知道客户端所处的补丁版本和能力层级
这套体系真正重要的,不是让修改更快,而是让快速修改依然在可控范围内。
常见误区
最常见的错误,是把热更新当成测试替代品,觉得有问题可以线上再修。第二类错误,是只建设下载和替换能力,不建设兼容、灰度和回滚能力。第三类错误,是热更新边界没有先定,结果脚本、配置、资源和宿主接口一起跨版本漂移,最后补丁链本身变成事故来源。
实战案例:一次「校验全绿,灰度桶崩了四倍」的热更兼容排查
案例为教学示例(非摘自真实项目),数字经过取整简化。它把本页「一套热更新体系通常不只是一条下载链」「最危险的问题是兼容性,而不是下载失败」「回滚能力为什么必须优先级极高」「灰度不是附加优化,而是事故隔离机制」「常见误区」用到深水区:热更新的最后一道验收不在下载校验,而在兼容矩阵与补丁层级——这是一次兼容缺口被灰度桶拦下的完整排查。
背景与现象:一款长线运营项目,宿主大版本 2.4~2.7 并存(最老支持 2.4)。为修活动结算脚本的一个发奖错误,发布脚本热更补丁;下载、校验、签名全绿,按设备分桶灰度 5%,40 分钟后灰度桶告警:崩溃率 0.3% → 1.2%(4 倍),其余 95% 用户尚未暴露。
第一步:现象与口径——先把「崩了四倍」按宿主分桶
口径统一:崩溃率取崩溃平台的会话崩溃占比口径,按宿主大版本分桶对照。补丁前后各记一笔:
- 补丁前:全量基线 0.3%
- 灰度桶:1.2%,增量 0.9 个百分点(4 倍)
增量不是均匀分布的:崩溃堆栈 100% 落在新脚本的调用栈上,宿主分布集中在 2.4/2.5——先分桶,再算兼容账。
第二步:分层归因——从下载校验到运行时的补丁链账本
传输账 补丁下载成功率 99.8%、签名校验 100% 通过——传输层无责
版本账 灰度桶宿主分布:2.6+ 占 60%(崩溃率 0.2%),2.4/2.5 占 40%(崩溃率 2.7%)
合成账 60%×0.2% + 40%×2.7% = 1.20%——与桶内实测 1.2% 对上,崩溃集中在老宿主
接口账 崩溃栈顶是活动配置门面接口——它在宿主 2.6 才暴露,2.4/2.5 一调用就抛
止损账 告警到停用下发通道 22 分钟;原计划 T+2 小时全量,灰度桶拦下 95% 暴露面账本收束:传输链路全绿(下载 99.8%、签名 100%),崩溃全部来自运行时兼容缺口——新脚本调用的门面接口只在宿主 2.6+ 存在,占桶 40% 的 2.4/2.5 一进活动就抛。本页「补丁可能下载和校验都成功了,但运行时才开始崩」逐字应验。
第三步:根因——兼容矩阵缺了「脚本 API 最低宿主版本」这一行
对照本页「一套热更新体系通常不只是一条下载链」的六层能力:内容组织、下载校验签名、灰度放量都在,缺的是「与宿主版本的兼容判断」没管到接口粒度——兼容矩阵只登记「补丁 ↔ 宿主大版本可否安装」,没登记「脚本调用的每个门面接口最低宿主版本」,2.4 这样的老宿主就漏进了灰度桶。回滚四问对照更难堪:「服务端是否知道客户端当前补丁层级」当时答不出——服务端只知道「装没装」,不知道「装到哪一层」,停用通道只能阻断新加载,已加载端要靠重启走本地兜底回落。本页「没有灰度,热更新的事故半径通常会远大于整包发版」反向成立:拦住这次事故的正是 5% 的桶;而常见误区第三类「脚本、配置、资源和宿主接口一起跨版本漂移,最后补丁链本身变成事故来源」就是这次差点应验的形态。
第四步:处置与回填——停通道是止血,回补丁层才是恢复
修法分两段。应急三步:停用补丁下发通道(告警后 22 分钟);已加载端引导重启、回落到上一稳定补丁层——活动缓存按补丁层版本化,回落无脏状态;为 2.4/2.5 单出兼容版补丁(接口存在性守卫 + 降级路径),按同分桶灰度重放。复核:停用后 30 分钟崩溃率回落至 0.3% 基线;兼容版重放同分桶持平后放量。制度三步:脚本 API 最低宿主版本写入兼容矩阵,发布流水线按矩阵拦截(缺登记不发);回滚演练细到补丁层粒度,服务端登记每端所处层级;分层测试清单补上「与当前宿主版本的兼容测试」,矩阵内最老两个大版本必跑冒烟。
回填清单:
| 落点 | 动作 | 回填处 |
|---|---|---|
| 兼容门禁 | 脚本 API 最低宿主版本入兼容矩阵,流水线按矩阵拦截发布 | 最危险的问题是兼容性,而不是下载失败 |
| 层级回滚 | 回滚演练细到补丁层,服务端登记每端所处补丁层级 | 回滚能力为什么必须优先级极高 |
| 灰度隔离 | 兼容类补丁灰度桶必含最老支持宿主,观察期按宿主分桶看崩溃 | 灰度不是附加优化,而是事故隔离机制 |
| 兼容测试 | 「与历史宿主版本兼容」进分层测试清单,最老两个大版本必跑冒烟 | 热更新和测试是什么关系 |
案例的三个教训
- 校验全绿只证明补丁完整送达,不证明它能在每个宿主上跑——兼容矩阵要细到接口粒度,它是热更的最后一道验收。
- 灰度是事故隔离机制,不是流程仪式——5% 的桶把一次接口缺口摁在了 95% 用户之外。
- 停用通道只是止血,回滚到补丁层才是恢复——服务端答不出「每端装到哪一层」,止损就永远缺一半。
Used By
热更体系设计;灰度与回滚机制评审;热更兼容回归流程。
- KBEngine(commit
0bc93d5):脚本级热重载的实现样例——baseapp/cellapp 注册reloadScript(kbe/src/server/baseapp/baseapp.cpp:445、kbe/src/server/cellapp/cellapp.cpp:175),核心虚函数逐实体重载(kbe/src/lib/server/entity_app.h:1452,实现:1441-1447(同文件))——重载的是脚本而非 C++ - Nakama(commit
e920249):对照的「重启式」边界——三语言运行时 Lua/JS/Go(内置 gopher-lua forkserver/runtime_lua.go:37、gojago.mod:12),但 Reload/hotswap 在server/全量 grep 无命中,脚本更新走进程重启
Related
关系链:解决什么 → 能力清单 → 兼容性 → 回滚 → 灰度 → 测试关系 → 体系化做法 → 常见误区 → 实战案例。上游:scripting/03(热更的对象)。下游:production/03(灰度回滚与兼容窗口的发布面)。
Reference
本页为工程经验归纳;页内实战案例为教学示例(数字取整简化)。