Skip to content

可靠性、顺序与兼容性 ​

Category ​

networking / 传输语义。节点定位:把可靠性、顺序、兼容性拆成三种独立语义分别决策。

Definition ​

“可靠送达”“顺序正确”“跨版本兼容”这三个词经常被一起提,但它们不是一件事。可靠不代表有序,有序不代表兼容,兼容更不意味着业务语义不会被误用。把这三件事混在一起,是很多网络设计和协议治理问题的起点。

Problem ​

可靠、有序、兼容三件事混着谈,「这条消息到底允许丢吗」就会变成各说各话——排障时的争执大多从这里来。先拆语义,再谈策略。

先把三种语义拆开 ​

可靠性 ​

关注的是消息最终有没有被送达、处理或确认。典型场景包括:

  • 支付结果。
  • 资产发放。
  • 结算确认。
  • 关键状态迁移。

顺序 ​

关注的是消息以什么相对次序被消费。典型场景包括:

  • 连续输入帧。
  • 聊天消息。
  • 房间事件序列。
  • 同一对象的状态变化。

兼容性 ​

关注的是不同版本客户端和服务端能否对同一条消息保持一致理解。典型场景包括:

  • 灰度发布。
  • 多端并行。
  • 渠道包延迟更新。
  • 热更新与旧包共存。

这三者会互相影响,但必须分开治理。

为什么不同消息要不同语义 ​

同一个项目里,不同消息对这三种语义的需求差异很大:

  • 角色资产变更必须可靠,通常还要幂等,顺序往往也重要。
  • 位移同步更看重新消息,晚到的旧消息很多时候应直接丢弃。
  • 聊天消息通常需要相对顺序,但不应该阻塞战斗主链路。
  • 活动通知可以稍晚、可以重试,但不值得拖累高频交互。

如果把所有消息都套进同一语义,系统就会在不必要的地方付出额外时延和复杂度。

可靠性的工程问题不只是重发 ​

很多人一想到可靠性,就想到“没收到就重发”。这只解决了一小部分问题。真正的可靠语义通常还包括:

  • 有没有唯一请求 ID 或消息 ID。
  • 重复到达时能否幂等处理。
  • 服务端处理成功但回包丢了怎么办。
  • 客户端超时重试会不会导致重复副作用。

也就是说,可靠性的核心不只是传输,而是最终处理语义是否可靠。

顺序也不是只有全局有序 ​

顺序最容易被过度简化。真实项目里更常见的是:

  • 同一连接内相对顺序。
  • 同一对象或同一房间内局部顺序。
  • 同一玩家操作流顺序。

并不是所有消息都需要全局总顺序。事实上,过度追求全局顺序往往会带来很高成本和没必要的阻塞。

例如:

  • 同一个角色的移动更新需要局部顺序。
  • 两个独立外围系统的通知未必需要互相保序。
  • 新位置到了以后,旧位置更新完全可以作废。

兼容性不能靠传输层顺便解决 ​

兼容性经常被错误地打包进“可靠协议”里。实际上它是更高层的契约治理问题,通常依赖:

  • 版本协商。
  • 字段默认值。
  • 向前和向后兼容规则。
  • 枚举扩展规则。
  • 兼容窗口管理。

这和 TCP 还是 UDP 没有直接关系。传输层能送过去,不代表接收方理解得对。

常见的组合策略 ​

成熟系统通常不会给所有消息统一上“可靠 + 有序 + 兼容”三件套,而是按类型组合:

  • 关键控制消息:强调可靠、幂等、明确回执。
  • 输入或状态消息:强调时效性,允许覆盖旧值或只要求局部顺序。
  • 外围通知消息:强调最终送达,但不阻塞主链路。
  • 跨版本高风险消息:强调协议治理和兼容窗口。

这种分层的好处是,系统不会在所有地方都背最高成本。

业务层、协议层、传输层该怎么分工 ​

更现实的看法是:

  • 传输层或协议层可以承载部分可靠和顺序语义。
  • 业务层必须自己定义幂等、覆盖、过期和补偿规则。
  • 兼容性治理通常属于协议契约和版本管理层。

如果这三层责任不分,最后就会出现一种局面:传输层觉得问题在业务层,业务层觉得问题在协议层,出了事故谁都解释不清。

一组更实用的判断问题 ​

设计某条消息时,可以强制自己回答:

  • 如果这条消息丢了,会发生什么。
  • 如果它重复到了,会发生什么。
  • 如果它晚到了,还要不要处理。
  • 如果它和上一条顺序反了,会不会出事。
  • 如果旧客户端收到它,会不会误解。

这几个问题答完,消息语义通常就清楚很多。

常见误区 ​

  • 要求“所有消息都可靠、都有序、都兼容”,结果代价极高且并不必要。
  • 完全不区分语义,把问题留给业务代码补救,最终每个模块都发明一套自己的重试和幂等逻辑。
  • 把兼容性误当成传输问题,结果新旧版本一并上线时整条链路一起抖。

这一页真正要强调的,不是三种概念的定义,而是复杂度必须分层,不同消息只应该背自己值得背的语义成本。

实战案例:一次「重连后道具多领了一份」的重复与顺序排查 ​

案例为教学示例(非摘自真实项目),数字经过取整简化。它把本页「可靠性的工程问题不只是重发」「顺序也不是只有全局有序」「一组更实用的判断问题」用到深水区:传输层一直在重发,账却对不上——这是弱网期重复副作用与旧值覆盖的排查,也是「怎样给每类消息标注语义」的完整展开。

背景与现象:某放置养成手游,地铁、电梯场景重连高发。月末对账发现结算发奖流水比客户端到账记录多 217 笔,重复率 0.9%;同期「位置回退」「进度倒退」类投诉日 340 单。两类问题都集中在重连后的 10 秒窗口内。

第一步:现象与口径——先按「丢了、重复了、晚到了」分账 ​

对账口径:发奖流水 24100 笔,客户端到账 23883 笔,差 217 笔;抽 20 笔重复样本比对,服务端每笔都收到了两次请求,处理了两次,发了两次奖。

幂等口径:重复的两笔请求内容完全一致,但服务端没有唯一请求 ID,无法区分「新请求」和「重试」——传输层忠实重发,业务层照单全收。

顺序口径:位置回退投诉的样本里,客户端先收到新位置、后收到旧位置,旧位置包直接覆盖了新位置——位移同步包没有时间戳,也没有覆盖规则。

第二步:分层归因——按本页三层分工逐层排查 ​

text
月末差异账 217 笔重复 + 340 单回退  归因
  回包丢失 + 客户端超时重试,业务层无幂等     217 笔重复的全部来源
  位移包无版本号,旧值覆盖新值                340 单回退的全部来源
  传输层可靠 ≠ 业务可靠:重发解决送达,不解决重复

排查路径:传输层重发正常工作(该到的都到了);协议层也没有丢字段;问题出在业务层——发奖接口没有幂等表,位移同步没有局部顺序与覆盖规则。这正是本页说的「传输层觉得问题在业务层,业务层觉得问题在协议层,出了事故谁都解释不清」。

第三步:根因——可靠语义只做了一半,顺序语义没有做 ​

可靠性只在传输层做了重发,业务层没有幂等:回包丢失后客户端超时重试,服务端把重试当新请求再发一次奖。顺序上追求的只是「都送到」,同一对象的局部顺序和晚到旧值的覆盖规则完全没有定义。两类缺口叠加,弱网重连窗口就成了重复与回退的高发区。

第四步:处置与回填——按本页五问给每类消息标注语义 ​

  1. 发奖类消息补唯一请求 ID 与幂等表:重复到达直接返回首次处理结果,不再重复发奖;回包也带同一 ID,客户端凭 ID 对账。
  2. 位移与状态类消息带时间戳和序号:晚到的旧值直接丢弃,新值到达后旧值作废,按同一对象的局部顺序处理,不做全局排序。
  3. 用本页五问逐类标注语义:丢了会怎样、重复了会怎样、晚到还处理吗、反序会出事吗、旧端会误解吗——每类消息的答案写进协议评审表。

复核账:重复发奖 217 笔 → 0 笔;位置回退投诉日 340 单 → 12 单;对账差异率 0.9% → 0.01%;幂等表上线首月命中 3.1 万次重复请求,全部正确返回首次结果。

回填清单:

回填项动作回填处
唯一请求 ID 与幂等表发奖、结算类消息带唯一 ID,服务端按 ID 幂等,重复到达返回首次结果可靠性的工程问题不只是重发
状态覆盖规则位移与状态包带时间戳和序号,晚到旧值直接丢弃,按对象局部有序顺序也不是只有全局有序
五问语义标注协议评审表按丢、重、晚、反、旧五问逐类标注,答案随协议变更同步更新一组更实用的判断问题
回执与补偿关键资产消息明确回执语义:处理成功回包丢失时,凭 ID 查证补偿,不盲目重发常见的组合策略

案例的三个教训 ​

  1. 重发解决送达,不解决重复,幂等必须落在业务层。传输层一直在忠实工作,账还是错了——可靠性问的是「最终处理语义可靠」,不是「包发了多次」。
  2. 顺序按对象局部有序设计,全局有序的成本不值。位置回退的全部根因就是没有定义「新值到了旧值作废」——一条覆盖规则抵得过整套全局排序。
  3. 语义标注要在出事前做,五问就是最短的检查单。丢、重、晚、反、旧五问答完,发奖类和位移类的语义差异一目了然,不用等对账差异来教。

Used By ​

协议评审时给每类消息标注语义;结算与支付类消息的强可靠设计;帧同步输入流的顺序设计。

上游:消息模式(05)、协议契约(04)。下游:帧同步与状态同步(server 树)。关系链:传输语义 → 同步模型 → 确定性。

Reference ​

本页为工程经验归纳;页内实战案例为教学示例(数字取整简化)。

游戏知识体系