通信模式
Category
networking / IPC。节点定位:按语义分通信模式(等不等结果、接收方是否唯一、是否广播、失败怎么算),再谈组件。
Definition
系统边界一旦明确,下一步就不是“用哪个中间件”,而是“这条边界到底该采用什么通信模式”。通信模式选错,后面即使用最成熟的组件,也只是把错误稳定地放大。
真正有价值的区分,不是按产品名词分,而是按语义分:调用方是不是要等结果,接收方是不是唯一,消息是不是天然广播,失败以后是立即报错、稍后补偿,还是允许静默重试。
Problem
「用哪个中间件」是最后才该回答的问题。模式归类错了,再成熟的组件也只是把错误稳定地放大:该等结果的地方用了 fire-and-forget,结算就变成玄学。
最常见的几类通信模式
同步请求-响应
这是最直观的一类模式。调用方发起请求,等待明确结果,再决定下一步。它适合:
- 玩家操作必须立即反馈
- 上下游必须共享同一次交互语境
- 出错需要立刻终止当前流程
- 调用结果直接影响当前主链路
它的优点是语义清楚,调用方最容易理解;缺点是耦合强、延迟直接暴露、下游抖动会立刻传到上游。
异步投递
调用方把任务或事件投递出去,不同步等待处理完成。这适合:
- 调用方更关心“交付出去”,而不是“立刻处理完”
- 下游可以稍后消费
- 需要削峰或缓冲
- 链路允许重试和补偿
它的优点是解耦和缓冲能力强;缺点是链路变得不那么直观,排障、顺序、重复处理和回执确认都会更复杂。
发布订阅
某个事件发生后,多个订阅者都可能需要知道并做各自处理。例如战斗结束后,战绩、排行、成就、报表、通知都要被触发。这种模式适合:
- 一个事实要被多个系统复用
- 上游不想知道具体有哪些下游
- 下游处理节奏可以彼此独立
它的代价是事件契约需要长期稳定,否则一次字段或语义变化会同时影响多个消费者。
请求接收 + 后台完成
这是一种很常见的混合模式。入口先同步确认“请求已受理”,真正的重操作在后台异步完成。它适合:
- 玩家需要立即得到受理反馈
- 后台处理耗时较长
- 最终结果可以稍后查询或推送
例如资产导入、排行榜重算、大型结算归档、离线补发等。它的关键不是模式本身,而是系统要把“受理成功”和“处理成功”明确区分。
模式选型首先要看什么
是否必须共享同一个交互时刻
玩家点击按钮后,如果必须立刻知道成功或失败,说明更偏同步。如果调用方只需确保事件被可靠接受,下游什么时候完成并不影响当前交互,就更偏异步。
下游是否只有一个
一个请求只对应一个明确处理者时,同步调用往往更简单;一个事实需要多个下游同时感知时,事件或发布订阅通常更自然。
失败是否必须立刻暴露
有些失败必须在当前交互里返回,例如进入副本失败、购买失败;有些失败可以延后恢复,例如报表汇总失败、通知发送失败。失败暴露时机不同,通信模式通常也不同。
系统能否承受积压和延迟不确定性
异步系统的代价不是“更慢”,而是“完成时刻不确定”。如果业务根本承受不了这种不确定性,就不应该为了所谓解耦把它消息化。
常见模式组合
真实项目里,通信模式通常不会单独存在,而会组合出现:
- 同步入口 + 异步后处理
- 命令调用 + 事件广播
- 本地同步推进 + 跨系统异步联动
例如房间结算可能先同步产出权威结果,再异步广播给排行、成就和报表系统。这样做的好处是主链路仍然可解释,外围链路又能获得解耦。
通信模式变化,本质上是在移动复杂度
很多团队误以为把同步调用改成异步,就等于把复杂度降低了。实际上你只是把复杂度从“调用时等待”移动成了:
- 回执确认
- 重试逻辑
- 消息顺序
- 消费幂等
- 补偿与审计
- 延迟观测与积压治理
所以通信模式不是“同步老旧、异步先进”,而是“复杂度应该在哪一层被承担”。
常见误区
最常见的错误,是把异步理解成架构升级,遇到链路就先消息化。第二类错误,是把一个事实同时当命令和事件使用,导致调用方既想同步拿结果,又假装自己是解耦的。第三类错误,是只在设计时画模式图,不去补充确认语义、失败语义和可观测性,最后所有模式都变成不稳定的混合体。
实战案例:一次「兑换码点了没回执,玩家连点六次」的通信模式错配排查
案例为教学示例(非摘自真实项目),数字经过取整简化。它把本页「模式选型首先要看什么」「请求接收 + 后台完成」「通信模式变化,本质上是在移动复杂度」用到深水区:该等结果的地方用了 fire-and-forget——这是兑换回执的悬空排查,也是「模式归类错了,再成熟的组件也只是把错误稳定地放大」的完整展开。
背景与现象:某发行平台把兑换码链路从同步请求-响应改为 fire-and-forget 投递,口号是「全面异步化」。改造前提交到回执 P99 240 毫秒;改造后玩家点「兑换」界面无回执,平均 4.1 秒才刷新,1.2% 的兑换请求无回执悬空;玩家以为没成功反复提交,单人最多连点 6 次,重复提交工单日均 260 起。
第一步:现象与口径——回执、重复、悬空三笔账
回执口径:玩家点击「兑换」→ 界面得到明确的成功或失败,才算这次交互完成。三笔账分开记:
- 回执账:改造前 P99 240 毫秒,改造后平均 4.1 秒——玩家看到的是「处理中」,心里想的是「是不是没成」。
- 重复账:无回执逼出重复提交,工单日均 260 起,单人最多连点 6 次。
- 悬空账:1.2% 的请求投出去既没回执也没失败记录,对账只能人工捞。
第二步:分层归因——按本页四问逐项对
兑换回执失控归因 占比
fire-and-forget 用在必须共享交互时刻的链路 55%
「受理成功」与「处理成功」未区分,回执无人负责 30%
复杂度搬家六项无人承接,回执与重试悬空 15%逐项比对:第一问「是否必须共享同一个交互时刻」的答案是必须,链路却用了异步投递;「请求接收 + 后台完成」要的受理与处理分离没有做;复杂度搬家清单(回执确认、重试逻辑、消息顺序、消费幂等、补偿与审计、延迟观测与积压治理)一项都没落。三类都在,交互时刻错配那条占比最大。
第三步:根因——模式归类先于组件,顺序反了
本页 Problem 原话:模式归类错了,再成熟的组件也只是把错误稳定地放大——该等结果的地方用了 fire-and-forget,结算就变成玄学。团队先定「全面异步化」再看交互,没回答「是否必须共享同一个交互时刻」,把必须当场出结果的兑换,当成了可以稍后消费的事件。
第四步:处置与回填——把复杂度搬家的账接回来
- 模式回退:兑换提交改同步请求-响应当场拿回执;资产导入、排行重算类改「请求接收 + 后台完成」,受理与处理分开。
- 搬家清单接管:回执确认、重试逻辑、消息顺序、消费幂等、补偿与审计、延迟观测与积压治理六项逐项落责任人。
- 四问进评审:是否共享交互时刻、下游是否唯一、失败是否立刻暴露、能否承受积压,答完再选模式。
复核账:兑换回执 P99 4.1 秒 → 180 毫秒;重复提交工单 260 起/日 → 11 起/日;无回执悬空 1.2% → 0.03%。
回填清单:
| 回填项 | 动作 | 回填处 |
|---|---|---|
| 交互时刻四问 | 必须共享交互时刻的链路禁用 fire-and-forget | 模式选型首先要看什么 |
| 受理处理分离 | 同步先回「已受理」,后台完成另发结果通知 | 请求接收 + 后台完成 |
| 复杂度搬家清单 | 回执、重试、顺序、幂等、补偿、观测六项逐项落位 | 通信模式变化,本质上是在移动复杂度 |
| 模式组合基线 | 同步入口加异步后处理为默认,命令与事件分开发 | 常见模式组合 |
案例的三个教训
- 异步不是架构升级,是复杂度搬家——搬家清单没人接,回执和幂等就变成悬空账。
- 回执是交互的一部分——玩家点了就要有明确结果,重复提交是缺回执的必然结果,不是玩家的问题。
- 模式归类先于组件选择——归类错了,再成熟的组件也只是把错误稳定地放大。
Used By
跨服务调用评审;消息中间件选型前的模式归类。
Related
上游:IPC 边界(01)。下游:低延迟与高吞吐取舍(04)、方案选型(06)。关系链:通信模式 → 延迟/吞吐取舍 → 方案选型。
Reference
本页为工程经验归纳;页内实战案例为教学示例(数字取整简化)。