消息模式与事件分发
Category
networking / 消息模式。节点定位:请求响应、推送、广播、订阅、内部事件流的边界与分工。
Definition
请求响应、服务器推送、房间广播、世界广播、订阅分发、内部事件流,这些不只是通信风格差异,而是在决定系统如何组织状态传播。消息模式选错,复杂度就会落到错误的地方:要么主链路被外围流量绑架,要么每个业务模块各自发明一套消息语义。
为什么消息模式必须按语义拆开
游戏链路里天然同时存在几种完全不同的交互:
- 登录、购买、进房这种“我发起一个动作,并等待明确结果”的请求响应。
- 房间状态变化、战斗事件、位置更新这种“服务端主动告诉你发生了什么”的推送。
- 房间或场景里多人共享状态时的广播。
- 活动、邮件、好友上线这类外围通知。
- 服务内部的异步事件,例如结算后通知战绩、活动、埋点或审计系统。
如果这些交互全被做成一种模式,问题一定会慢慢积累。
Problem
请求响应适合什么,不适合什么
请求响应最适合那些需要明确因果和明确回执的场景:
- 登录。
- 进房。
- 购买。
- 抽卡。
- 领取奖励。
- 配置拉取。
它的价值在于:
- 因果关系清晰。
- 错误处理清晰。
- 客户端状态机更容易组织。
但它不适合承载持续状态同步和高频广播。因为一旦你把“状态变化”也强行做成“发请求等回包”,系统会变得链路冗长、回包臃肿,而且会让服务端为大量无意义确认付成本。
推送和广播在游戏里不可避免
只要玩家需要共享同一空间、同一房间或同一活动状态,服务端就一定需要主动推送状态和事件。典型场景包括:
- 房间里其他玩家准备状态变化。
- 战斗中的技能命中、血量变化、对象创建销毁。
- 场景频道和世界级公共事件。
- 活动开启、结束和结算。
广播的本质不是“发给所有人”,而是“谁应该知道什么变化”。这一步设计不好,带宽和客户端消费成本都会迅速失控。
内部事件流和外部协议层不要混成一套
服务内部也需要事件分发,例如:
- 结算完成后触发战绩写入。
- 支付成功后触发发货和审计。
- 玩家升级后触发任务和成就进度。
这些内部事件流和对客户端的网络协议层,不应该混成一个概念。原因很简单:
- 内部事件更关注解耦、重试、幂等和异步消费。
- 外部协议更关注时延、回包语义、会话状态和玩家体验。
如果把两者强行统一进一套“万能消息框架”,最后通常两边都服务不好。
广播最该先考虑什么
广播真正贵的,不只是发出去,而是:
- 发给谁。
- 发什么粒度。
- 多频繁发。
- 客户端如何消费。
比较稳妥的广播设计通常会先做分层:
- 房间内全员必收广播。
- 房间内局部对象广播。
- 场景或频道级广播。
- 可延迟、可合并、可降级的外围广播。
没有这层区分,系统很快就会退化成“有变化就全量发,谁用谁自己过滤”。
事件分发边界为什么容易失控
消息模式一旦没有边界,最常见的症状是:
- 客户端为了一个结果被动等待过多回包。
- 服务端同时维护多套相似广播逻辑。
- 一个简单状态变化引发大量下游同步抖动。
- 外围系统为了消费事件而侵入主链路。
所以成熟系统通常会刻意区分:
- 主链路请求响应。
- 主链路状态推送。
- 房间或场景广播。
- 后台异步事件。
概念看起来变多了,实际上是在减少系统长期混乱。
一种更贴近实践的分工方式
比较稳妥的设计往往是:
- 强交互流程保留为明确请求响应。
- 战斗和房间状态变化用推送或广播。
- 外围联动走异步事件流。
- 广播前先做对象分层和接收者裁剪。
- 让客户端明确哪些消息会主动来,哪些消息必须自己发起请求。
这样做的好处是:
- 主链路更可控。
- 外围链路更解耦。
- 性能和优先级更容易隔离。
消息模式会直接影响服务边界
这不是抽象讨论。你采用怎样的消息模式,通常会直接反映到服务组织方式上:
- 所有东西都走同步请求响应,服务会更强耦合。
- 状态同步和事件流分层后,外围服务更容易异步化。
- 广播语义明确后,房间服和场景服职责会更清楚。
也就是说,消息模式不是通信美学,而是服务边界设计的一部分。
常见误区
- 把所有消息都塞进统一分发框架里,结果既没有链路边界,也没有优先级隔离。
- 广播策略粗暴到“有变化就全量发”,把网络和客户端都拖进高成本更新。
- 把内部事件流和客户端协议层混成一套,导致异步解耦和实时反馈两边都做不好。
消息模式设计真正要回答的,不是“选哪种风格”,而是每类状态变化到底应该通过哪种传播方式离开当前模块。
实战案例:一次「一套万能消息框架,把主链路和外围一起拖垮」的边界失控排查
案例为教学示例(非摘自真实项目),数字经过取整简化。它把本页「为什么消息模式必须按语义拆开」「广播最该先考虑什么」「一种更贴近实践的分工方式」用到深水区:所有消息都进了同一套框架——这是消息模式没有边界的排查,也是「每类状态变化各走各的传播方式」的完整展开。
背景与现象:某项目把 5 类消息(请求响应、状态推送、房间广播、外围通知、内部事件)塞进同一套分发框架,没有链路边界也没有优先级隔离。晚高峰战斗推送延迟 P99 1200 毫秒,埋点与审计事件和技能结果挤同一条队列;房间广播「有变化就全量发」,带宽 70% 被全量广播占走。
第一步:现象与口径——先看谁在挤谁
影响面口径:战斗推送延迟 P99 1200 毫秒,玩家看到的是血量变化晚半拍;外围事件与主链路消息共用队列,互相等待。
广播口径:全量广播带宽占比 70%,客户端为一个结果被动等待过多回包,本地还要自己过滤一遍。
第二步:分层归因——按本页消息模式分工排查
主链路被一起拖垮归因 占比
万能框架无链路边界与优先级隔离 50%
有变化就全量发、无接收者裁剪 32%
内部事件流与客户端协议混一套 18%逐项比对:本页说「如果把两者强行统一进一套“万能消息框架”,最后通常两边都服务不好」;广播没有先答“谁应该知道什么变化”,全量发出谁用谁自己过滤;结算、埋点的内部事件侵入主链路。三类都在,万能框架那条占比最大。
第三步:根因——把消息模式当成了通信风格之争
本页 Problem 说得直接:消息模式选错,复杂度就会落到错误的地方:要么主链路被外围流量绑架,要么每个业务模块各自发明一套消息语义。团队把「选哪种风格」当成美学问题,没有回答每类状态变化到底该通过哪种传播方式离开当前模块。
第四步:处置与回填——按语义把传播方式拆开
- 五类各走各的:强交互流程保留明确请求响应;战斗与房间状态走推送或广播;外围联动走异步事件流;广播前先做对象分层和接收者裁剪。
- 广播四层裁剪:房间全员必收、房间局部对象、场景频道级、可延迟可合并可降级的外围广播,逐层控制粒度与频率。
- 主链路优先级隔离:请求响应、状态推送、房间广播、后台异步事件四条链分开治理,客户端明确哪些消息会主动来、哪些必须自己发起请求。
复核账:战斗推送延迟 P99 1200 毫秒 → 40 毫秒;全量广播带宽占比 70% → 18%;5 类消息全部按语义分流。
回填清单:
| 回填项 | 动作 | 回填处 |
|---|---|---|
| 语义拆分五类 | 请求响应、推送、广播、外围通知、内部事件各走各的 | 为什么消息模式必须按语义拆开 |
| 广播四层裁剪 | 全员必收、局部对象、频道级、可降级外围分层,先答发给谁 | 广播最该先考虑什么 |
| 分工落地五条 | 强交互走响应、状态走推送广播、外围走事件流、广播先裁剪 | 一种更贴近实践的分工方式 |
| 框架边界哨兵 | 禁万能框架、禁全量广播、禁内外事件混一套 | 常见误区 |
案例的三个教训
- 广播的本质不是“发给所有人”,而是“谁应该知道什么变化”。这一步不答,带宽和客户端消费成本都会迅速失控。
- 消息模式不是通信美学,而是服务边界设计的一部分。消息怎么走,服务就怎么拆。
- 内外事件必须分两套。内部事件关注解耦、重试、幂等;外部协议关注时延、回包语义和体验,混成一套两边都做不好。
Used By
玩法系统选下发通道(技能结果走响应、Buff tick 走推送、房间状态走广播);服务拆分时划内部事件流与外部协议层的界。
Related
上游:游戏网络基础(01)。下游:房间广播与弱网(07)、AOI(server 树,世界广播的可见性裁剪)。关系链:消息模式 → 广播 → 可见性裁剪 → 增量下发。
Reference
本页为工程经验归纳;页内实战案例为教学示例(数字取整简化)。