IPC 基础与场景边界
Category
networking / IPC。节点定位:系统拆开之后,跨边界传递状态与动作的语义边界。
Definition
IPC 和消息系统真正解决的,不是“进程之间怎么通信”这种抽象问题,而是系统拆开之后,状态和动作如何跨边界传递。进程、线程、服务一旦被拆开,原来进程内一次函数调用就能完成的事情,都会变成下面这些更现实的问题:
- 调用方要不要同步等结果
- 消息重复到达怎么办
- 顺序错了还能不能恢复
- 下游处理不过来时,上游是阻塞、丢弃还是积压
- 一条链路失败后,谁来补偿和重试
所以讨论 IPC,不能从“有没有消息队列”开始,而要从“这条边界上到底在传什么语义”开始。
为什么系统拆开以后必须认真面对 IPC
单进程系统里,模块之间通信大多是进程内调用,失败语义相对简单,状态可见性也更直接。系统一旦走到多进程或多服务,边界上的问题会立刻变多:
- 调用不再免费,序列化、拷贝、调度和网络延迟都会出现
- 失败从“抛异常”变成“超时、重复、乱序、半成功”
- 排障从读本地堆栈变成追跨进程链路
- 版本演进开始涉及协议兼容和灰度窗口
也就是说,IPC 不是简单换了一种 API,而是让模块边界第一次真正暴露出成本。
Problem
哪些问题天然适合消息化
消息化最有价值的场景,通常具备下面这些特征:
- 生产方不必同步等待结果
- 允许消费者按自己的节奏处理
- 链路更关注削峰和解耦,而不是最低延迟
- 同一个事件需要被多个下游复用
- 下游失败不应该直接拖垮上游主流程
典型例子包括:
- 日志采集和埋点上报
- 战斗结束后的战绩、排行、报表更新
- 邮件、推送、补偿、通知
- 运营活动的外围联动
- 跨进程的后台任务分发
这类链路本质上更像“把事实发布出去,再让下游各自处理”。
哪些问题不该轻易消息化
如果一条链路同时要求下面这些特征,就要非常谨慎:
- 强实时
- 强顺序
- 强交互反馈
- 上下文高度耦合
- 出错后难以补偿
例如:
- 局内战斗输入处理
- 每帧对象同步
- 场景控制权实时切换
- 玩家点击后必须立刻得出明确结果的关键交互
这些链路如果被粗暴地改造成消息队列驱动,系统往往会失去可解释性。延迟从毫秒级尾部抖动变成积压不确定性,错误也从“立即失败”变成“过几秒才知道某个消费者没处理好”。
IPC 不是只有 MQ 一种形态
很多团队谈 IPC 就只想到消息队列,这是典型误区。实际工程里常见形态至少包括:
- 进程内事件总线
- 共享内存、管道、socket 等本地 IPC
- RPC 或 gRPC 这类同步调用
- RPC 配合后台任务的混合模式
- 基于 Broker 的异步消息系统
它们解决的是不同问题。是否引入 Broker,取决于你要的到底是低延迟调用、跨边界解耦、广播能力,还是削峰与缓冲能力。
判断场景边界时,先看这四件事
1. 调用方是否必须立刻知道结果
如果必须立刻知道结果,这条链路通常更偏同步调用;如果只需要把事件可靠交出去,下游什么时候处理完可以稍后再看,就更适合消息化。
2. 失败代价由谁承担
有些失败必须直接反馈给玩家,例如购买失败、进入副本失败;有些失败可以进入补偿或重试,例如排行刷新、报表更新、推送下发。失败代价不同,通信方式通常也不同。
3. 顺序和幂等要求有多强
如果消息重复一次、乱序一次就会直接破坏状态,说明这条边界上还需要更强的控制,不适合轻率异步化。
4. 链路是单消费还是多消费
如果一个事件天然要被多个系统消费,消息化会明显更自然;如果只是一个调用方等待一个处理结果,硬上 MQ 往往只是在引入额外复杂度。
一个更贴近工程现实的边界判断
可以把 IPC 链路粗分成两类:
- 命令型链路:更像“请你现在帮我做这件事,并返回明确结果”
- 事件型链路:更像“这件事已经发生了,你们各自决定怎么处理”
很多通信设计做坏,就是因为命令和事件没分清。把命令伪装成事件,调用方会失去确定性;把事件伪装成命令,系统又会背上不必要的同步耦合。
常见误区
最常见的错误,是一谈跨进程通信就直接上 MQ,把消息系统当成万能总线。第二类错误,是只看中间件能力,不看链路本身的状态语义、失败代价和兼容窗口。第三类错误,是没有区分命令型和事件型边界,结果同步和异步语义混在一起,系统越来越难解释。
实战案例:一次「进副本点了没反应,十秒后才报失败」的场景边界误判排查
案例为教学示例(非摘自真实项目),数字经过取整简化。它把本页「判断场景边界时,先看这四件事」「IPC 不是只有 MQ 一种形态」「一个更贴近工程现实的边界判断」用到深水区:命令型链路被当事件发了出去——这是进副本反馈的悬空排查,也是「把命令伪装成事件,调用方会失去确定性」的完整展开。
背景与现象:某 MMO 把「进副本」交互链路从同步 RPC 一刀切成 MQ 异步投递。改造前点击到明确反馈 P99 180 毫秒;改造后玩家平均等 2.6 秒才有回执,失败场景 10 秒后才弹提示;进副本失败工单从日均 30 起涨到 420 起;一次链路问题定位从 10 分钟拖到 2 小时。
第一步:现象与口径——把「没反应」拆成三笔账
反馈口径:玩家点击「进入副本」→ 界面得到明确的成功或失败,才算这次交互完成。三笔账分开记:
- 反馈账:改造前 P99 180 毫秒,改造后平均 2.6 秒,失败反馈拖到 10 秒。
- 失败账:失败工单日均 30 起 → 420 起,多数是「点了没反应」之后的重复点击与追问。
- 排障账:链路从读本地堆栈变成追跨进程链路,一次定位 10 分钟 → 2 小时。
第二步:分层归因——按本页四件事逐项对
进副本反馈失控归因 占比
命令伪装成事件,同步语义被整条异步化 60%
失败代价错配,该立刻反馈的失败进了补偿队列 25%
IPC 形态收敛成单一 MQ,排障要跨进程追队列 15%逐项比对:第一问「调用方是否必须立刻知道结果」的答案是必须,链路却按事件发了出去;第二问「失败代价由谁承担」的答案是直接反馈给玩家,失败却进了补偿队列;第四问的单消费链路被硬上 MQ。三类都在,命令伪装成事件那条占比最大。
第三步:根因——四件事没答完就选了形态
本页原话:把命令伪装成事件,调用方会失去确定性;延迟从毫秒级尾部抖动变成积压不确定性。团队先定「全面消息化」再看链路,等于把「请你现在做这件事并返回明确结果」的命令,伪装成「这件事已经发生了,你们各自决定怎么处理」的事件——确定性当场消失,反馈变成排队。
第四步:处置与回填——把形态选择权还给语义
- 链路回退:进副本、购买、进场景确认回同步 RPC,日志、排行、推送类保留消息化,混合形态各归其位。
- 四问进评审:新链路立项先答「立刻知道结果、失败代价、顺序幂等、单/多消费」四问,命令型与事件型判别写进链路文档。
- 形态清单恢复:进程内总线、本地 IPC、RPC、Broker、后台任务按语义分工,禁止「一谈跨进程通信就直接上 MQ」。
复核账:进副本反馈 P99 2.6 秒 → 170 毫秒;失败工单 420 起/日 → 25 起/日;链路定位 2 小时 → 8 分钟。
回填清单:
| 回填项 | 动作 | 回填处 |
|---|---|---|
| 场景边界四问 | 立刻知道结果、失败代价、顺序幂等、单/多消费四问进立项评审 | 判断场景边界时,先看这四件事 |
| 命令事件判别 | 命令型走同步或同步入口加异步后处理,事件型才进消息系统 | 一个更贴近工程现实的边界判断 |
| 形态选择清单 | 进程内总线、RPC、Broker、后台任务按语义分工,不默认上 MQ | IPC 不是只有 MQ 一种形态 |
| 不消息化清单 | 强实时、强顺序、强交互的链路禁入消息化改造 | 哪些问题不该轻易消息化 |
案例的三个教训
- 「能不能立刻拿到结果」先于「用什么中间件」——四件事没答完就选形态,等于倒着做设计。
- 失败代价决定反馈通道——必须直接反馈给玩家的失败放进补偿队列,问题只是被推迟暴露。
- 形态单一化是排障成本——五种 IPC 形态收敛成一种,排障从读本地堆栈变成追跨进程链路。
Used By
服务拆分评审(哪些调用允许异步化);结算链路设计(外围事件化与主链路同步的分界)。
Related
上游:服务拆分(server 树)。下游:通信模式(02)、主/外围链路(03)、幂等与顺序(05)。关系链:拆分边界 → IPC 语义 → 通信模式 → 幂等与顺序。
Reference
本页为工程经验归纳;页内实战案例为教学示例(数字取整简化)。