游戏后端线上事故复盘:一个"冗余字段"引发的战队超员与排行榜事故
一、业务与系统背景
这是我之前做游戏后端时遇到的一次线上事故,涉及游戏内的战队系统。
- 业务规则:每支战队人数上限为 90 人,玩家入队时系统会判断是否已满员。
- 系统设计 :
- 核心表
UnionDefine(战队基础表):冗余字段MemberCount,用于快速判断当前人数。 - 核心表
UnionMember(战队成员表):按战队 ID 分表,存储每位玩家的成员记录。
- 核心表
- 附加系统:全服战队战力排行榜,每 12 小时刷新一次。
二、问题发现与影响评估
事故由玩家投诉触发:有玩家反馈,某战队实际人数超过了 90 人上限。
- 公平性影响 :由于多出了一名成员的战力,该战队直接冲上全服排行榜第一 ,而其正常排名应在 60 名左右,严重破坏了排行榜公平性。
- 数据核查 :
- 基础表
MemberCount显示为 90 人,恰好达上限。 - 成员表
UnionMember统计有效成员,实际为 91 人 。 - 全服扫描发现,共有 5 支战队存在计数与真实人数不一致的问题,其中 1 支已突破上限。
- 基础表
三、排查过程与根因定位
3.1 排查思路
计数不准无非两种可能:
- 入队时多加。
- 离队时多减。
3.2 排除入队逻辑
入队有三种方式(创建、邀请同意、申请同意),均复用同一段公共代码,且在锁内串行执行,并发安全有保障,因此初步排除入队环节。
3.3 根因定位:离队逻辑漏洞
重点审查离开逻辑(主动退出 / 被踢出),发现该流程通过异步线程 提交至战队单线程执行,但缺少严格的前置二次校验。
- 核心漏洞 :
- 离队前执行权限校验
validateQuitPermission,逻辑为"只要不是队长即可退出"。 - 若玩家已被移出战队 ,查询职位接口查不到记录,会返回一个默认值(该值不等于队长 ID)。
- 权限校验因此意外通过,导致离队流程被重复执行。
- 离队前执行权限校验
- 漏洞触发链路 :
并发场景(如同时触发踢出与主动退出) ->MemberCount被多扣减 1 次 -> 入队判断误以为未满 -> 允许超员加入 -> 最终人数突破 90 人上限,战力与排行榜同步异常。
四、修复与止损方案
由于涉及排行榜公平性,优先级定为 P0 级事故 ,通过热更紧急修复,共分五步执行:
- 代码漏洞修复 :补全
validateQuitPermission逻辑,当查询职位返回错误值(玩家不在战队)时,直接禁止执行离队流程,从源头堵住重复扣减。 - 异常数据修正 :将超员战队中最后加入的 1 名玩家 移出,并手动修正
MemberCount,确保人数合规。 - 排行榜紧急修复:手动触发全服战力重算与排名更新,迅速恢复榜单公平性。
- 全量数据巡检 :编写脚本扫描所有战队,将剩余 4 支计数不一致的战队全部修正,避免问题扩散。
- 用户触达:通过游戏内邮件向被移出的玩家说明原因。因入队无道具消耗,未作额外补偿。
五、复盘与长效改进
5.1 暴露的四类问题
- 设计冗余风险 :
MemberCount冗余字段增加了一致性维护成本。 - 并发校验缺失:异步线程操作未做"二次状态校验"。
- 代码规范问题:查询不到数据返回默认值,易被上层误用。
- 幂等性不足:核心计数变更流程缺少幂等保障。
5.2 对应长效优化措施
- 架构演进 :规划逐步去掉
MemberCount冗余字段,入队判断改为成员表实时计数 + 缓存方案,减少双写不一致。 - 通用校验组件:将"二次状态校验"抽成通用方法,强制要求所有离队/踢出场景复用,避免逻辑分散。
- 编码规范升级 :将可能返回空值的查询接口改为返回
Optional,强制调用方处理空场景,从编译期规避 NPE 及误用。 - 流程幂等改造 :调整执行顺序:先删除成员记录,确认成功后再更新计数,从流程上杜绝重复扣减。
- 主动监控告警 :增加每日数据一致性巡检任务,发现不一致主动告警,将"被动处理"转变为"主动发现"。