无状态后台可以滚动重启,有状态游戏服务不能这么粗暴。房间服里有战斗,场景服里有玩家位置,网关上挂着长连接。一次普通版本发布,如果没有架构支持,就会变成“凌晨发版仍然踢掉一批玩家”。2021 年很多团队开始把发布从运维动作前移到服务设计里,因为有状态服务能不能发布,取决于它平时是否允许状态被关闭、转移和恢复。
核心判断
- 有状态服务发布的核心是先停止进入,再处理存量,最后验证退出
- 版本共存不是可选项,只要客户端不能同时更新,就必须设计协议兼容
- 回滚要回滚流量和规则,不能只回滚二进制
架构示意
flowchart TD
Plan["发布计划"] --> Mark["标记待摘流实例"]
Mark --> StopNew["停止新会话/新房间"]
StopNew --> Drain["等待或迁移存量"]
Drain --> Check["状态一致性检查"]
Check --> Upgrade["升级实例"]
Upgrade --> Canary["小流量验证"]
Canary --> Full["恢复调度"]
Canary --> Rollback["回滚流量与配置"]
发布协议从服务注册开始
有状态服务要能发布,服务注册信息里就不能只有地址和端口。它应该暴露 version、state、capacity、acceptingNew、drainDeadline、supportedProtocol、buildId。调度器创建房间或分配连接时只选择 acceptingNew 的实例;老实例进入 draining 后仍然服务存量,但不接新玩家。这样发布系统不需要理解每个玩法细节,也能统一完成第一步:停止进入。
存量处理有三种策略
有状态服务的存量会话通常有三种处理方式:自然结束、快照迁移、强制终止。实时对战适合自然结束,设置最大等待时间;开放世界场景可以做快照迁移,把玩家位置、对象状态和局部事件转到新实例;低价值临时玩法可以强制终止并补偿。不要假装所有状态都能迁移,也不要把所有状态都强制踢掉。架构设计要允许每种业务声明自己的 drain policy。
版本共存要从协议开始
发布期间一定会出现新旧服务共存,新旧客户端共存。协议字段要向前兼容,新字段有默认值,旧字段保留足够时间。服务间调用也要标注版本能力,例如新房间服能否接受旧匹配服务的 launch 请求。最危险的是配置版本:新代码读新配置,旧代码读旧配置,如果玩家在两个版本间切换,规则可能不一致。发布架构需要把代码版本、协议版本、配置版本绑定成 release bundle,并记录每个会话使用的 bundle。
回滚不是重启旧镜像
很多团队以为回滚就是把镜像切回上一版。有状态服务里,回滚还包括停止新版本流量、冻结新规则写入、处理已经进入新版本的会话、恢复旧配置、确认数据结构是否可逆。如果新版本已经写入旧版本不认识的数据,直接回滚会制造更大事故。因此发布前要写 downgrade plan:哪些字段只读兼容,哪些字段延迟写入,哪些功能必须灰度到可关闭。
发布控制面与业务面的边界
发布控制面负责实例状态、流量权重、摘流进度、健康检查和回滚命令;业务面负责声明自己能否摘流、如何迁移、何时安全退出。不要让发布脚本直接改业务数据库,也不要让业务服务自己决定全局发布节奏。好的边界是控制面发出 drain intent,业务面回报 drain status。控制面根据超时和策略决定等待、迁移或中止。
玩家体验保护
发布期间最重要的指标不是机器升级完成率,而是玩家体验损伤。应该监控发布窗口内断线率、重连成功率、房间创建失败率、匹配等待时间、战斗异常结束数、客服工单关键词。客户端也要配合显示明确提示,比如“服务器维护中,当前对局结束后更新”,而不是突然连接断开。对于长局游戏,可以提供发布保护区:临近维护时不再开启可能超过窗口的新局。
一次发布演练怎么做
真正上线前,可以挑一组测试服模拟:创建长房间,启动发布,确认新房间不再进入旧实例;kill 掉 draining 实例,确认存量能重连或补偿;让新旧客户端同时在线,确认协议兼容;发布到一半触发回滚,确认调度器不再把玩家送到新版本;最后检查 release bundle 记录是否可追踪。发布架构不是为了让发版显得复杂,而是让复杂性在白天演练过,不在凌晨事故里第一次出现。
工程落地表
| 关注点 | 推荐做法 | 常见风险 |
|---|---|---|
| 状态边界 | 明确权威服务、缓存副本和可恢复事实 | 把运行态散落在多个服务里,故障时无法判断谁说了算 |
| 版本控制 | 给协议、配置、策略和数据结构都记录版本 | 发布后新旧逻辑交错,排查时无法复现 |
| 失败补偿 | 每个跨服务步骤都设计超时、重试和幂等结果 | 成功路径能跑通,异常路径留下脏状态 |
| 观测指标 | 指标贴近玩家体验,同时保留技术细分维度 | 只有机器指标,事故发生时不知道玩家卡在哪 |
| 演练方式 | 用脚本制造重试、掉线、超时、重启和版本不一致 | 只在测试服点几次正常流程,线上第一次遇到边界 |
一个可执行的落地步骤
第一步,不急着重构所有代码,而是把 有状态服务发布 的关键事件和状态列出来,形成一张状态表。表里至少要有事件来源、状态 owner、是否可重试、是否需要持久化、失败后谁补偿。很多团队会在这一步发现,线上所谓的随机故障其实是状态没有 owner。
第二步,先在边界处加版本和审计。即使内部实现暂时没改,只要每次请求、每次状态转换、每次跨服务调用都能留下版本、原因和结果,后续迭代就有依据。不要等事故后再补日志,那时最关键的上下文已经丢了。
第三步,挑一条高价值路径做闭环,例如登录进房、领取奖励、切换场景或活动开启。闭环要包含成功、重复、超时、失败、回滚和人工处理。只要一条路径跑通,团队就能把模式复制到其他路径。
第四步,把演练自动化。有状态服务发布 的风险大多不会在正常点击里出现,而是在进程重启、网络抖动、配置切换、客户端重试、下游超时的组合里出现。自动化演练不需要一开始很复杂,能稳定复现三五个最危险场景,就已经比靠人工记忆可靠。
复盘问题清单
- 玩家在最差网络条件下,是否仍然能得到明确结果,而不是一直转圈?
- 服务重启或发布时,是否有清晰的进入、等待、迁移和退出策略?
- 重复请求、延迟响应和旧会话消息是否会污染新状态?
- 关键决策是否能通过日志复现,包括输入、版本、策略和输出?
- 如果下游服务短暂不可用,当前架构是保护玩家体验,还是把错误直接扩散到客户端?
- 运维或客服是否有安全的人工介入入口,还是只能直接改数据库?
在实际落地 有状态服务发布 时,团队还需要把责任边界写进代码和文档。第 1 个容易被忽略的点,是不要让临时判断散落在调用方。调用方只表达意图,平台层给出明确结果,业务层再根据结果决定是否继续。这样做看似多了一层接口,后续排查却非常省时间:日志能说明哪个版本的策略参与了决策,指标能看到哪个阶段开始变慢,回滚时也能只回滚策略而不是重启整组服务。对于游戏服务器来说,很多架构问题最终都会落到玩家体验上,稳定的边界比聪明的捷径更重要。
在实际落地 有状态服务发布 时,团队还需要把责任边界写进代码和文档。第 2 个容易被忽略的点,是不要让临时判断散落在调用方。调用方只表达意图,平台层给出明确结果,业务层再根据结果决定是否继续。这样做看似多了一层接口,后续排查却非常省时间:日志能说明哪个版本的策略参与了决策,指标能看到哪个阶段开始变慢,回滚时也能只回滚策略而不是重启整组服务。对于游戏服务器来说,很多架构问题最终都会落到玩家体验上,稳定的边界比聪明的捷径更重要。
在实际落地 有状态服务发布 时,团队还需要把责任边界写进代码和文档。第 3 个容易被忽略的点,是不要让临时判断散落在调用方。调用方只表达意图,平台层给出明确结果,业务层再根据结果决定是否继续。这样做看似多了一层接口,后续排查却非常省时间:日志能说明哪个版本的策略参与了决策,指标能看到哪个阶段开始变慢,回滚时也能只回滚策略而不是重启整组服务。对于游戏服务器来说,很多架构问题最终都会落到玩家体验上,稳定的边界比聪明的捷径更重要。
在实际落地 有状态服务发布 时,团队还需要把责任边界写进代码和文档。第 4 个容易被忽略的点,是不要让临时判断散落在调用方。调用方只表达意图,平台层给出明确结果,业务层再根据结果决定是否继续。这样做看似多了一层接口,后续排查却非常省时间:日志能说明哪个版本的策略参与了决策,指标能看到哪个阶段开始变慢,回滚时也能只回滚策略而不是重启整组服务。对于游戏服务器来说,很多架构问题最终都会落到玩家体验上,稳定的边界比聪明的捷径更重要。
在实际落地 有状态服务发布 时,团队还需要把责任边界写进代码和文档。第 5 个容易被忽略的点,是不要让临时判断散落在调用方。调用方只表达意图,平台层给出明确结果,业务层再根据结果决定是否继续。这样做看似多了一层接口,后续排查却非常省时间:日志能说明哪个版本的策略参与了决策,指标能看到哪个阶段开始变慢,回滚时也能只回滚策略而不是重启整组服务。对于游戏服务器来说,很多架构问题最终都会落到玩家体验上,稳定的边界比聪明的捷径更重要。
发布窗口里的人工操作
有状态服务发布不能完全依赖自动化。自动化负责稳定执行,人工负责在风险出现时做选择。控制台至少要展示每个实例的状态、存量会话数、最老会话持续时间、drain 剩余时间、异常退出数和当前版本。发布负责人看到某个实例长时间无法清空时,可以选择延长等待、触发迁移、阻止新开长局,或者按预案补偿终止。
这些操作必须有权限和审计。凌晨发布时,最危险的不是没有按钮,而是按钮没有边界。强制终止应该要求填写原因,影响玩家数超过阈值要二次确认,回滚配置要记录 release bundle。这样第二天复盘时,团队知道每一步为什么发生,而不是只看到一堆服务重启记录。
数据兼容的最低要求
有状态服务发布前,至少要确认三类数据兼容。第一,旧代码能否读到新代码写入的字段;第二,新代码能否处理旧实例留下的运行态快照;第三,回滚后是否会重复执行已经完成的补偿或奖励。对游戏来说,最怕的是发布到一半新版本改变了结算语义,回滚后旧版本又按旧语义补发一次。发布架构必须把数据兼容纳入检查项,而不是只看镜像能不能启动。
总结
游戏有状态服务发布架构设计 的重点不在于堆更多组件,而在于把状态、时间、版本和失败路径讲清楚。游戏服务器的复杂度通常不是来自单个算法,而是来自玩家行为、网络环境、运营动作和服务故障同时发生。一个可信的架构,应该让正常路径足够顺,让异常路径有边界,让每一次自动处理和人工介入都有证据可查。做到这一点,系统即使不能避免所有问题,也能把问题限制在可理解、可恢复、可继续迭代的范围内。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。