Skip to content

低延迟与高吞吐取舍 ​

Category ​

networking / IPC。节点定位:延迟、吞吐、顺序、可靠、成本五个目标的拉扯与定价。

Definition ​

消息系统里最常见的幻觉,是以为可以同时轻松拿到低延迟、高吞吐、强顺序、强可靠和低成本。现实通常不是做不到其中一个,而是你每强化一个目标,另外几个目标就会开始涨价。

所以讨论通信方案时,不能只看平均吞吐或峰值 QPS,而要先问清楚:这条链路更怕慢,还是更怕积压;更怕抖动,还是更怕丢;更需要立刻完成,还是更需要在高峰期稳住。

低延迟和高吞吐为什么天然拉扯 ​

要追求极低延迟,通常意味着:

  • 减少排队
  • 减少批处理
  • 减少中间环节
  • 尽量靠近调用方和执行方

要追求极高吞吐,通常意味着:

  • 接受排队和缓冲
  • 接受批量处理
  • 接受更深的异步链路
  • 接受消费者按自己的节奏拉取

这两组策略天然存在张力。也就是说,消息系统能帮你提高总体吞吐,但它常常会把单条消息的完成时刻变得不那么确定。

Problem ​

哪些链路更偏低延迟优先 ​

更偏低延迟优先的链路,通常具备这些特征:

  • 直接影响玩家当前交互
  • 对尾延迟非常敏感
  • 一次处理的上下文很小,但时间窗口很紧
  • 积压本身就等于业务失败

例如:

  • 局内实时输入与反馈
  • 关键状态确认
  • 玩家进入或离开某个实时实例

这类链路与其说追求大吞吐,不如说更怕任何不可预测的排队。

哪些链路更偏高吞吐优先 ​

更偏高吞吐优先的链路,通常是:

  • 单条消息不要求马上完成
  • 允许批量处理
  • 高峰流量明显
  • 业务可以接受秒级甚至分钟级延迟

例如:

  • 埋点上报
  • 日志采集
  • 报表和统计
  • 批量通知
  • 历史归档

这类链路真正要解决的是高峰期别把系统打穿,而不是单条消息尽快被处理。

消息队列为什么擅长削峰,也擅长制造积压 ​

队列的价值之一就是把瞬时高峰摊平,让生产者和消费者解耦。但同一套机制也会带来另一个现实:如果消费者长期跟不上,系统不会立即报错,而是以积压的形式把问题藏起来。

积压一旦发生,后果通常不是简单“慢一点”,而是:

  • 事件价值随时间下降
  • 顺序窗口被拉长
  • 失败重试叠加更多压力
  • 业务看到的状态越来越滞后

所以削峰不是免费能力,它必须和容量规划、背压策略、过期策略一起设计。

真正要设计的是背压,而不是只设计吞吐 ​

一条链路流量上来以后,迟早要面对一个问题:下游处理不过来怎么办。常见选项无非几种:

  • 阻塞上游
  • 限流丢弃
  • 持续积压
  • 降级处理
  • 只保留最新值或关键值

不同链路的答案完全不同。埋点、日志允许采样和丢弃;玩家关键结算不能丢,只能重试或补偿;实时状态广播可能宁可覆盖旧消息,也不愿保留所有历史。

如果背压策略没有先定,所谓高吞吐只是把风险换成了未来某个时刻的系统性积压。

平均性能没意义,尾部性能才决定体验 ​

很多通信方案看起来吞吐很漂亮,但一看尾延迟和积压恢复时间就不行。对游戏系统来说,这尤其关键:

  • 平均 5ms 但偶发 500ms,玩家感知的是 500ms
  • 平均每秒处理十万条,但高峰恢复要十分钟,业务感知的是十分钟

所以性能判断不能只看均值,还要看:

  • P95 / P99 延迟
  • 积压增长速度
  • 高峰恢复时间
  • 下游故障时的退化行为

一个更接近工程现实的取舍方式 ​

比较稳妥的做法通常是:

  • 主链路优先保证延迟和确定性
  • 外围链路优先保证吞吐和解耦
  • 高频链路尽量减少 Broker 和持久化环节
  • 大吞吐链路必须设计积压治理与过期策略

这不是保守,而是承认不同链路真正怕的不是同一种问题。

常见误区 ​

最常见的错误,是拿批处理链路的吞吐思维去设计实时链路。第二类错误,是只看生产速度,不看消费者长期处理能力,结果队列长期积压。第三类错误,是完全没有背压和过期策略,默认所有消息都应该一直排下去,最后把问题从“现在慢”拖成“以后全线滞后”。

实战案例:一次「晚高峰队列越积越多,排行刷新拖到九秒」的背压缺失排查 ​

案例为教学示例(非摘自真实项目),数字经过取整简化。它把本页「消息队列为什么擅长削峰,也擅长制造积压」「真正要设计的是背压,而不是只设计吞吐」「平均性能没意义,尾部性能才决定体验」用到深水区:积压不报错,所以积压最危险——这是高峰积压的尾部排查,也是「如果背压策略没有先定,所谓高吞吐只是把风险换成了未来某个时刻的系统性积压」的完整展开。

背景与现象:某项目把埋点、日志与结算外围事件(排行刷新、成就进度)混在同一条 MQ 管道里,按吞吐指标选型,消费组没有背压与过期策略。晚高峰进场流量 8 万条/秒(日常 1.2 万条/秒的近七倍),积压峰值 1400 万条,恢复耗时 22 分钟;期间排行刷新 P99 从 180 毫秒涨到 9 秒,均值只从 180 毫秒涨到 260 毫秒;高峰期业务告警 60 起。

第一步:现象与口径——积压、尾部、告警三笔账 ​

积压口径:高峰进场 8 万条/秒,消费组处理不及,缺口全变积压,峰值 1400 万条、恢复 22 分钟——系统不报错,只是越来越滞后;事件价值随时间下降、顺序窗口被拉长、失败重试叠加更多压力、业务状态越来越滞后。

尾部口径:排行刷新 P99 从 180 毫秒涨到 9 秒,均值只从 180 毫秒涨到 260 毫秒——玩家和业务感知的是尾部,不是均值。

告警口径:高峰期业务告警 60 起,全部集中在排行刷新与外围事件超时——是积压的下游症状,不是独立故障。

第二步:分层归因——按本页背压与尾部清单排查 ​

text
晚高峰积压失控归因  占比
  背压策略没先定,所有消息默认一直排队    50%
  用批处理吞吐思维设计实时外围链路    30%
  只看生产速度,不看消费长期处理能力    20%

逐项比对:本页背压五选一(阻塞上游、限流丢弃、持续积压、降级处理、只保留最新值或关键值),系统实际默默选了「持续积压」且无人决策;埋点日志允许采样和丢弃,却与结算外围共用一条管道;尾部四项指标(P95/P99、积压增长速度、高峰恢复时间、下游故障时的退化行为)只报了吞吐。60 起告警逐项归位:30 + 18 + 12 = 60,账能对上。

第三步:根因——背压是链路设计的第一问,被跳过了 ​

本页原话:如果背压策略没有先定,所谓高吞吐只是把风险换成了未来某个时刻的系统性积压;积压一旦发生,系统不会立即报错,而是以积压的形式把问题藏起来。团队没做「这条链路更怕慢,还是更怕积压」的定价步骤,高吞吐的验收指标成了唯一指标。

第四步:处置与回填——先定背压,再谈吞吐 ​

  1. 链路分级:结算外围事件独立管道优先保延迟,埋点日志独立管道保吞吐,高频链路尽量减少 Broker 和持久化环节,大吞吐链路配积压治理与过期策略。
  2. 背压与过期先定:每条链路先答阻塞、丢弃、积压、降级、保最新五选一,积压超阈值按过期策略清退,高峰自动降级埋点。
  3. 尾部指标进大盘:P95/P99、积压增长速度、高峰恢复时间、下游故障退化四项列为链路 SLO,均值只做参考。

复核账:积压恢复 22 分钟 → 90 秒;排行刷新 P99 9 秒 → 160 毫秒;高峰告警 60 起 → 5 起。

回填清单:

回填项动作回填处
链路分级主链路低延迟优先、埋点报表高吞吐优先,分管道治理一个更接近工程现实的取舍方式
背压五选一阻塞、丢弃、积压、降级、保最新,每条链路先定再上线真正要设计的是背压,而不是只设计吞吐
过期削峰策略积压超阈值按价值过期清退,削峰配容量规划一起设计消息队列为什么擅长削峰,也擅长制造积压
尾部口径P95/P99、积压增速、恢复时间、故障退化进 SLO平均性能没意义,尾部性能才决定体验

案例的三个教训 ​

  1. 平均值是给报表看的,玩家感知尾部——平均 5ms 但偶发 500ms,玩家感知的是 500ms。
  2. 积压不报错,所以积压最危险——问题被藏起来,等到业务全线滞后才暴露。
  3. 背压是链路设计的第一问——没定背压的高吞吐,只是把风险换成未来的系统性积压。

Used By ​

链路分级(主链路低延迟优先、报表高吞吐优先);队列容量与背压设计;尾部延迟 SLO 制定。

上游:通信模式(02)。下游:背压与流控(server 树)、容量扩缩(server 树)。关系链:目标定价 → 背压 → 尾部监控 → 容量。

Reference ​

本页为工程经验归纳;尾部延迟口径见页内「平均性能没意义,尾部性能才决定体验」一节;页内实战案例为教学示例(数字取整简化)。

游戏知识体系