Skip to content

模块导航: 总入口 · 总纲与方法 · 后端场景 · AI与算法工程 · 国企与金融 · 网络问答


87. 组内加消费者想加速,结果速度没变(Kafka 消费者组) ​

【考察内容】Kafka 消费模型

【题目】Kafka 消费速度不够,你往消费者组里加了几台机器,结果速度几乎没变。为什么?请讲清消费者组与分区的关系:为什么同一个分区不能被组内多个消费者同时消费?并行度由什么决定?

【参考答案】

  1. 概念:分区是 Kafka 的并行存储单元;消费者组(group)内的消费者共同消费一个 topic,分区分配给组内消费者(一个分区分配给一个消费者,一个消费者可消费多个分区);
  2. 为什么同组不能多消费者共用一个分区:
    • 顺序性:分区内消息有序,若多消费者并发消费同分区,处理顺序无法保证;
    • offset 管理:分区进度(offset)以“消费者组+分区”为单位提交,多消费者并发会导致 offset 提交互相覆盖(A 处理到 10,B 处理到 5,提交错乱);
  3. 推论:分区数=组内最大并行度——消费者数超过分区数时,多余的消费者空闲;分区数决定吞吐上限;
  4. 不同组可以消费同一分区(各组独立 offset,互不影响)——用于多业务各自消费同一份数据;
  5. Rebalance:消费者增减/分区变化触发再平衡,分区重新分配(可能引起重复消费,需幂等)。
  6. 容量/并行度估算:消费者组内有效并行度 = min(消费者实例数, 分区数)。若 topic 有 8 个分区,拉起 20 个消费者也只有 8 个在干活,空转 12 个。目标吞吐反推:峰值 2 万 msg/s、单消费者稳定 2000/s → 需要 ≥10 个分区 + ≥10 个消费者。加机器前先看:分区数、单消费者处理 RT、是否有共享下游瓶颈(DB 连接池打满则扩消费者无效)。Rebalance 频率过高也会吃掉吞吐——扩缩容要平滑。
  7. 失败与降级:消费者实例故障触发 Rebalance,期间分区短暂无主,lag 上涨;处理:会话超时调合理、避免频繁滚动发布、用协作式再均衡。若下游容量有限,正确做法是消费端限流而不是无限加消费者。监控:每消费者分配到的分区数、lag、再均衡次数、单条处理耗时。发现“加机器不加速”先查分区分配再查下游。

【原理溯源】

  • 为什么并行度由分区数决定? 分区是 Kafka 的互斥消费单元:组内对每个分区只有一个活跃消费者。消费者只是“执行体”,没有更多分区可领就只能空转。所以加机器前先看 num.partitions。

  • 为什么 offset 以“组+分区”为单位? 消费进度必须能确定性地回答“这个组对这个分区读到哪了”。若多个消费者共写同一进度,会出现覆盖与回退。独占分区让进度语义简单可靠。

  • 不同组为什么可以并行消费同一分区? 各组维护独立 offset,互不干扰。这实现了“一份数据、多个业务各自消费”(如计费组、风控组、数仓组),是 pub/sub 的核心能力。

  • Rebalance 的代价是什么? 暂停消费(延迟)、可能从旧 offset 重放(重复)、分区迁移抖动。频繁 Rebalance(心跳超时、处理太慢)会恶化积压。优化:提高 max.poll.interval.ms、减少单次处理耗时、使用静态成员/协作协议。

  • 单消费者多线程行不行? 可以提升处理并行,但必须自己保证:同分区(或同 key)消息有序与 offset 提交正确。常见做法:拉取线程 + 按 key 分片的工作线程池 + 按分区提交。复杂度高,先优先加分区。

  • 元理论对应: 原理 5「不可靠信道上的正确性协议」(题群见《核心考点与元理论》§5.7)

【选型判断树】

text
消费慢如何加速?
├─ 消费者数 < 分区数 → 直接加消费者
├─ 消费者数 ≥ 分区数
│   ├─ 允许改 topic → 加分区(评估顺序影响)+ 加消费者
│   ├─ 不能改分区 → 单消费者内多线程(自行保序与提交)
│   └─ 消费逻辑本身慢 → 优化逻辑/批量/异步下游
└─ Rebalance 频繁 → 先修稳定性再谈扩容

口诀:分区是并行度上限;加机器不加分区常常无效。

【口述骨架】(5 分钟;框架 B,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“速度没变通常是因为消费者数已超过分区数”
0:30–2:30模型组内分区独占分配;一个消费者可多分区;分区数=最大并行度
2:30–3:30两个为什么顺序性 + offset 管理
3:30–4:30扩展路径加分区、多线程、优化逻辑;Rebalance 与幂等
4:30–5:00收尾“扩容前先看分区数与消费瓶颈位置”

【关键数字】

参数经验值说明
最大并行度= 分区数组内
加分区影响key 映射变化,可能乱序需评估
Rebalance 耗时秒级~数十秒期间可能停顿
常见配比消费者数 = 分区数再多无益
单消费者线程拉取 1 + 处理 NN 需自管顺序
有效并行度min(消费者数, 分区数)多出的消费者空闲
扩容前置条件分区数足够且下游能扛否则先扩分区/限流
反推分区峰值÷单消费者能力2万/2000=至少10分区

【追问链】(三层)

L1|“如何提高消费速度?” → 加分区 + 加消费者;优化单条处理耗时(批量、异步、缓存);确认下游不是瓶颈。不要只加机器。

L2|“为什么一个分区不能被组内多个消费者同时消费?” → 因为分区是组内并行的最小分配单位,消费进度就是一个单调递增的 offset。若组内两个消费者同时持有同一分区,就会出现两份独立进度、两次提交:谁都无法判断哪些消息已被处理,Rebalance 时两个 offset 还会互相覆盖,结果不是重复就是丢失。代价是组内最大并行度 = 分区数,消费者多于分区数的部分只会空闲。注意这条限制只作用于同一个组:不同消费组可以各自消费同一分区、各记各的 offset。

L3|“消费太慢导致 poll 超时被踢出组,怎么处理?” → 提高单次 poll 条数后的处理时间上限(max.poll.interval.ms)、减少单次处理耗时、改用异步处理并控制提交点。根因是“拉取循环被长处理阻塞”,而不是网络问题。

【评分标准】

档位答案特征
60 分说“要加分区”
80 分讲清组与分区分配、并行度=分区数
95 分解释顺序与 offset 两个原因;谈 Rebalance、多线程消费的自管成本

【关联题】

【自测】

  1. 12 个分区,加到 20 个消费者,有几个在干活? 参考答案: 最多 12 个分到分区,其余空闲。
  2. 不同业务组能否消费同一分区? 参考答案: 可以。各组独立 offset,互不影响。
  3. 判断对错:消费慢就加消费者实例总能提速。 参考答案: 错。受分区数上限约束,且若瓶颈在下游或单条逻辑,加实例无效。

88. 消息重试了 10 次还是失败,一直卡住队列(死信队列) ​

【考察内容】死信队列是 MQ 可靠性治理高频题

【题目】有一条消息消费一直失败(比如数据格式非法),重试了 10 次还失败,占着队列头,后面的消息全被堵住。怎么处理?死信队列怎么设计?死信消息如何告警、人工介入、回放?

【参考答案】

  1. 概念:消息消费失败超过重试次数上限(常见工程口径 3~5 次;题干能重试到 10 次,正说明上限被配大了或没设退避——这是本题要先纠的错)后,转入死信队列(DLQ)——专门存放“处理不了”的消息,与正常队列隔离;
  2. 触发场景:业务异常(数据不合法)、依赖故障持续超时、消息格式错误;
  3. 处理机制:
    • RabbitMQ:死信交换机(DLX)+ 死信路由键,消息在队列中被拒绝/过期/超限后转发;
    • RocketMQ:重试队列(%RETRY%)消费失败自动重试,超过次数进死信队列(%DLQ%);
    • Kafka:无内置死信,需自建——消费失败写“失败 topic”或落库;
  4. 死信处理:
    • 告警通知:死信产生即告警(钉钉/短信),人工介入;
    • 死信消费端:读取死信,分析失败原因(日志/消息体),修复后重新投递(重建消息回原队列);
    • 定时重放:对可恢复的死信定时重试(如依赖恢复后);
    • 兜底:无法恢复的登记后人工/对账处理;
  5. 设计要点:死信消息要保留原始信息(原 topic、消息体、失败原因、重试次数)。
  6. 容量估算:若业务消息峰值 5000 msg/s,失败率 1%,则重试流量约 50 msg/s;若单消费者重试逻辑更慢(如人工接口 1s/次),死信与重试队列会快速堆积。重试队列容量按“峰值失败量 × 最大重试次数 × 消息体 × 保留天”估算。例:按题干那个被配错的 10 次上限算,50/s×10 次×1KB,保留 7 天 ≈ 约 300GB 量级(50×10×1KB ≈ 500KB/s × 604800s ≈ 302GB,再乘副本);换成第 1 条给的 3~5 次,同一式子只有约 91~151GB——重试上限把磁盘量级直接放大 2~3 倍,这就是本题要先纠 10 次的原因。生产上重试 topic 要与主 topic 隔离分区与消费者,避免重试流量拖垮主链路。
  7. 失败与降级:重试耗尽 → 进死信,非顺序消费的主队列必须立刻继续,不能被单条毒消息卡死;若该队列要求严格有序,只能阻塞该分区(保序必然牺牲局部可用,见第 82 题);死信可人工修复后重新投递,或对账补偿;对可丢弃的非核心消息,业务确认后可定期清理死信。监控死信堆积量与增长速率,超过阈值告警。禁止:无限原地重试、或为了“清死信”无限制并发重放把下游打挂。

【原理溯源】

  • 为什么要死信,而不是一直重试? 无限重试有三宗罪:① 毒消息(格式错误)永远失败,空耗资源;② 占住队列头/分区,阻塞后续消息(顺序消费时尤其致命);③ 重试风暴打挂下游。死信把“处理不了”从主通道剥离,主通道保持流动。

  • 为什么毒消息必须隔离? 数据非法类失败与“下游暂时超时”不同:重试一万次也一样。若不识别异常类型并快速进死信,会把瞬时故障策略误用到永久故障上。

  • Kafka 为什么没有内置 DLQ? Kafka 定位日志流,消费失败的语义交给应用。常见自建:失败发到 xxx.DLT topic、或写入失败表;由专门消费者处理。这也说明死信是消费端模式,不是存储端魔法。

  • 死信闭环为什么必须“告警 + 原因 + 可回放”? 只堆积不告警 = 静默数据丢失(业务以为在处理)。必须保留原 topic、消息体、异常栈、重试次数,才能修复后回放。回放要幂等,避免修好后重复副作用。

  • 与重试策略的关系? 死信是重试的终点。合理策略:可重试异常有限次指数退避;不可重试立即死信;总次数超限进 DLQ(见 90 题)。

  • 元理论对应: 原理 5「不可靠信道上的正确性协议」(题群见《核心考点与元理论》§5.7)

【选型判断树】

text
消费失败
├─ 异常类型?
│   ├─ 参数/格式/业务非法 → 不重试,直接死信
│   ├─ 下游超时/临时故障 → 有限次退避重试
│   └─ 未知 → 先按可重试,但设较低上限
├─ 超过重试上限 → 死信队列
│   ├─ 立即告警
│   ├─ 保留上下文(原 topic、body、异常、次数)
│   └─ 人工/自动修复后回放(幂等)
└─ 监控:死信量、失败率、重试次数分布

口诀:毒消息快进死信;瞬时故障才重试;死信必须可告警可回放。

【口述骨架】(5 分钟;框架 D,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“死信是重试的终点隔离区,防止毒消息堵死主通道”
0:30–2:00机制三家 MQ 实现差异(Rabbit DLX、Rocket %DLQ%、Kafka 自建)
2:00–3:30处理闭环告警 → 分析 → 修复回放 → 人工兜底
3:30–4:30设计要点上下文完整、幂等回放、与重试策略衔接
4:30–5:00收尾“没有死信的重试系统,等于把事故藏起来”

【关键数字】

参数经验值说明
重试上限3–5 次(业务);Rocket 默认 16之后进死信
退避指数 1s→2s→4s…防重试风暴
死信告警非 0 即告警不要等积压
原文保留必须含原 topic + 异常否则无法回放
回放幂等必须修好后可能重复投递
重试流量峰值×失败率5000×1%=50 msg/s
死信/重试存储失败量×重试次数×体×天×副本注意与主 topic 隔离
毒消息策略快速进死信,不阻塞主队列主链路可用性优先

【追问链】(三层)

L1|“死信消息还能再消费吗?” → 能。分析修复后重新投递原队列或专用回放通道。回放消费必须幂等,因为业务可能已部分执行。

L2|“顺序消费时,一条失败进死信,后面的怎么办?” → 不能直接跳过——跳过就等于让后面的消息越过失败的那条,顺序当场就破了。顺序消费的正确做法是阻塞当前队列、原地重试:RocketMQ 的 OrderListener 失败后挂起该队列并按最大次数重试,重试耗尽才投死信并人工介入;Kafka 侧要保序就不能并发重试,通常把失败消息写入侧链并冻结该 key/分区后续消息的处理。工程上更常见的选择是放弃严格顺序,改用状态机前置校验 + 版本号(版本号更小的旧消息直接丢弃),让乱序变成可识别、可丢弃的,而不是错账。

L3|“死信量突然暴涨说明什么?” → 可能:下游故障、上游 schema 变更不兼容、脏数据洪峰、或错误地把可重试问题当毒消息。先看失败原因分布,再决定回滚变更、修下游还是批量清洗回放。死信是系统健康度信号,不只是垃圾场。

【评分标准】

档位答案特征
60 分知道失败多次进死信
80 分说清触发条件、三家实现、告警回放
95 分区分毒消息与瞬时故障;讲清阻塞 vs 跳过对顺序影响;死信作为监控信号

【关联题】

【自测】

  1. 为什么要设死信而不是无限重试? 参考答案: 防止毒消息永久失败、阻塞通道、造成重试风暴;把不可处理消息隔离出来人工治理。
  2. Kafka 的死信一般怎么实现? 参考答案: 无内置,消费失败写入失败 topic 或落库,由专门消费者告警与回放。
  3. 判断对错:死信队列里的消息可以忽略。 参考答案: 错。死信常代表数据丢失或业务未完成,必须告警、分析、回放或对账补偿。

89. 订单 30 分钟未支付自动关单,怎么定时触发(延迟消息) ​

【考察内容】延迟任务与 MQ 结合

【题目】订单 30 分钟未支付自动关闭。用 MQ 的延迟消息能力怎么做?RocketMQ 延迟消息怎么实现(延迟级别)、RabbitMQ 的 TTL+死信方案怎么搭?各自的延迟精度和限制是什么?

【参考答案】

  1. RocketMQ 延迟消息:先分版本答:4.x 内置延迟等级(1s/5s/10s/30s/1m/.../2h 等 18 级),发送时设 delayTimeLevel,到点投递——简单可靠但只有固定档位;5.0 起另有定时/延时消息,可指定任意到点时刻(服务端默认秒级精度、默认最长 24 小时),本题这种 30 分钟需求在 5.x 可直接表达;
  2. RabbitMQ 延迟队列:TTL(消息过期时间)+ 死信交换机——消息进“延迟队列”设 TTL,过期后被死信路由转发到真正消费队列;TTL 可设任意时长(需注意队列级/消息级 TTL 语义差异);
  3. Kafka:无内置延迟,需自建:方案 A——发消息时带“目标执行时间”,消费端判断未到时间则重新投递/暂存(可用分层时间轮+重投);方案 B——时间轮(Netty HashedWheelTimer)在应用层管理;
  4. 对比:RocketMQ 延迟消息最省事;RabbitMQ 用 TTL+DLX 组合;Kafka 生态需自研或用时间轮;
  5. 生产建议:MQ 延迟消息为主 + 定时任务扫表兜底(延迟消息丢失/超时的订单由定时任务扫描“待支付超时”订单关闭),双保险。
  6. 容量估算:日订单 50 万,每单下单即发一条 30 分钟延迟消息(无法预知哪单会超时)→ 日延迟消息 50 万条,均值 ≈ 5.8 msg/s(50万÷86400);其中真正触发关单的按 30% 算约 15 万次/日。「数百 msg/s」要带突刺前提才算得出来:均匀只有 5.8/s,「晚 8 点这一小时占日量 20%」≈ 50万×0.2÷3600 ≈ 27.8/s;要到数百必须假设秒级突刺(如限量开抢:开抢 10 分钟内下单占日量 20% → 10 万单÷600s ≈ 167/s,延迟 30 分钟后集中到期)。所以消费能力按「突刺系数 × 峰值小时速率」配,并把突刺假设写出来,否则要么配空要么配爆。RocketMQ 延迟消息或 Redis ZSet+轮询都要按峰值到期量设计消费能力。存储要先定发法:方案 A 每单都发(下单即投一条延迟消息,到期再判是否超时)——日发 50 万条,在途量 ≈ 近 30 分钟下单量;方案 B 只对超时单发(日 15 万条)——但下单那一刻无法预知它会不会超时,所以 B 实际只能靠「先写状态 + 扫表兜底」实现,延迟消息省下的只是消息量,扫描与索引成本要加回来。本题正文与【关键数字】都按 A 计;用 B 就把 15 万带进所有下游算式(消费能力、Broker 存储、扫描 QPS)保持一致。A 下 30min 窗口内的待投递量 ≈ 近 30 分钟的下单量,按本题日订单 50 万、峰值小时占 20% 折算 ≈ 10 万单/h ÷ 60 = 1670 单/min,30 分钟约 5 万条 在途(其中约 30% 会真正到期关单 ≈ 1.5 万条)。关单消费要幂等,且与支付回调竞争同一状态机。
  7. 失败与降级:延迟消息丢失/投递失败 → 依赖兜底定时扫描(扫描 created_at 超过 30min 且未支付的订单);MQ 延迟精度误差(4.x 的预设延迟级别非任意时间;5.x 定时消息可指定任意到点)→ 业务用“到期后再校验是否已支付”;消费者宕机 → 消息重投,状态机保证只关一次;支付与关单并发 → 数据库状态条件更新(仅当 status=待支付 才关单)。监控:延迟消息堆积、关单成功率、超时未关单对账差异。

【原理溯源】

  • 为什么延迟消息不能“Broker 睡眠 30 分钟”? Broker 要服务海量消息,不能为每条延迟消息挂起线程。实现要么到期时间索引 + 调度投递(RocketMQ 定时/延迟服务),要么暂存后过期转发(Rabbit TTL+DLX),本质都是“先隔离,到点再进入可消费通道”。

  • 为什么 RocketMQ 只给固定延迟级别? 用有限级别把延迟消息映射到内部调度结构,实现简单、吞吐高。这是 4.x 的实现取舍;5.x 用分级时间轮+到点存储,可指定任意到点时刻(时长参数精确到毫秒,但服务端默认投递精度 1000ms≈秒级,默认最长 24 小时)。所以答案要按客户版本分两支:还在 4.x 就对齐到最近档位(30m)或用扫表精确关单,在 5.x 就定时消息为主、扫表仍留作兜底。

  • RabbitMQ TTL+DLX 的坑在哪? ① 队列级 TTL 与消息级 TTL 并存时,以先到期者为准,易误伤;② 消息只有到达队列头才检查过期,若前面有更长 TTL 的消息,后面的短 TTL 会被“卡住”——所以要每消息一队列或保证 TTL 单调。这是面试经典坑。

  • 为什么必须“延迟消息 + 扫表兜底”? 延迟消息可能丢(Broker 故障、消费失败、级别设错),而关单是资金相关正确性动作,不能只靠 MQ。定时任务扫描“创建超过 30 分钟且未支付”的订单做二次校验关闭,是最终一致的安全网。

  • 关单动作本身为什么要幂等? 延迟消息与扫表可能都触发关单;支付回调也可能并发。关单必须状态机幂等(仅“待支付”可关)。

  • 元理论对应: 原理 5「不可靠信道上的正确性协议」(题群见《核心考点与元理论》§5.7)

【选型判断树】

text
延迟/定时任务怎么做?
├─ 精度要求
│   ├─ 分钟级可接受 → MQ 延迟消息
│   └─ 秒级/任意时刻 → 时间轮或调度中心(如 XXL-JOB)+ 扫表
├─ MQ 能力
│   ├─ RocketMQ → delayLevel
│   ├─ RabbitMQ → TTL + DLX(注意头阻塞)
│   └─ Kafka → 自建时间轮/重投或不用 MQ 延迟
└─ 一律加扫表兜底(资金/状态类)

口诀:延迟消息是快路径,扫表是正确性路径,两者都要。

【口述骨架】(5 分钟;框架 B,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“30 分钟关单是延迟投递问题,主流用 MQ 延迟消息 + 扫表双保险”
0:30–2:30三家实现Rocket 等级、Rabbit TTL+DLX、Kafka 自建
2:30–3:30坑与限制固定档位、TTL 头阻塞、Kafka 无内置
3:30–4:30工程闭环扫表兜底、状态机幂等、与支付回调并发
4:30–5:00收尾“能用内置不用自研;资金动作必须有第二路径”

【关键数字】

参数经验值说明
RocketMQ 延迟级别4.x:18 级(1s–2h)固定档位;5.x:任意到点按版本分档
订单支付窗口常见 15–30 分钟与库存预占一致
扫表周期1–5 分钟兜底而非主路径
TTL 头阻塞短 TTL 可能被长 TTL 卡住Rabbit 经典坑
时间轮精度毫秒级内存方案,注意持久化
延迟消息量每单都发=日订单量(50 万/日,本题口径);只对超时单发=日订单×超时比例(15 万/日,需配扫表兜底)两式别混用
峰值到期下单峰×超时比例须给突刺假设:按每单都发口径均匀约 5.8/s、晚高峰一小时占日量 20% ≈ 27.8/s,「数百 msg/s」要按开抢级突刺算(10 分钟占日量 20% → ≈167/s)才成立

【追问链】(三层)

L1|“Kafka 说有延迟消息吗?” → 无内置。需消费端判断时间未到则重投/暂存,或应用层时间轮。多数团队在有 RocketMQ 时用 Rocket,或干脆用调度系统+扫表。

L2|“RabbitMQ 队列 TTL 和消息 TTL 混用有什么坑?” → 坑在过期检查只在队首触发。RabbitMQ 只判断队首消息是否过期,队列中间的消息即便 TTL 已到也不会被清理;一旦给每条消息设不同的 x-message-ttl,长 TTL 的消息压在队首,后面已过期的短 TTL 消息就迟迟不生效。队列级 TTL 是统一的、行为可预期;两者同时设置时按较短的算,但排查时很难看清是哪一层在生效。更要分清队列本身的空闲回收 x-expires(整个队列闲置多久被删)与消息 TTL 是两回事,面试里把它们混叫「队列 TTL」就会答错。实践口径:同一队列只用一种粒度且优先用队列级;要真延迟就换 RocketMQ 的延迟等级或外部时间轮(见本题 L1/L3 与第 92 题)。

L3|“延迟消息丢了,订单一直挂着怎么办?” → 扫表兜底:按创建时间扫描超时未支付订单,状态机校验后关闭并回滚库存。这就是为什么不能只依赖 MQ 延迟。同时对延迟消息本身做监控(发送量 vs 到期消费量)。

【评分标准】

档位答案特征
60 分知道 RocketMQ 有延迟消息
80 分三家方案都能说;知道 TTL+DLX
95 分指出4.x 固定档位限制(并知道 5.x 可指定任意到点时刻、默认秒级精度)与 TTL 头阻塞;强调扫表兜底与关单幂等

【关联题】

【自测】

  1. RocketMQ 延迟消息的主要限制? 参考答案: 分版本——4.x 只有 18 个固定延迟档位,任意时长需对齐到最近档位或配合扫表;5.x 起可指定任意到点时刻的定时/延时消息(时长参数到毫秒、服务端默认秒级精度、默认最长 24h)。无论哪版都要留扫表兜底,因为延迟消息本身可能丢。
  2. 为什么关单要扫表兜底? 参考答案: 延迟消息可能丢失或消费失败;关单影响库存与资金正确性,需要数据库扫描作为最终一致保障。
  3. 判断对错:RabbitMQ 中给每条消息设不同 TTL 就能实现任意延迟。 参考答案: 不完全对。存在头阻塞与队列/消息 TTL 交互问题,需谨慎设计。

90. 消费者一抛异常就无限重试,队列被堵死(消费重试策略) ​

【考察内容】消息消费的容错设计

【题目】消费者处理消息抛异常后,如果无限重试会把队列堵死;不重试又会丢消息。重试策略怎么设计才合理(重试次数、退避间隔、区分可重试/不可重试异常、超过次数进死信)?

【参考答案】

  1. 失败分类处理:
    • 可重试错误(下游超时、临时故障):自动重试,采用指数退避(1s、2s、4s...)或固定间隔,设最大次数(3~5 次);
    • 不可重试错误(消息格式错误、业务数据非法):重试无意义,直接进死信/记录告警(避免无限重试浪费);
  2. 实现方式:
    • RocketMQ:默认重试 16 次(延迟等级递增),可配最大重试次数;
    • RabbitMQ:消息 reject/nack + 重试队列(或延迟重试);
    • Kafka:消费端手动控制——捕获异常后不提交 offset,等待重投;或用“重试 topic”(失败消息发到重试 topic,延迟后重新消费);
  3. 关键防坑:
    • 重试风暴:大量消息同时失败重试,打爆下游——指数退避+熔断(下游故障时暂停消费,恢复后再继续);
    • 重试消息乱序:顺序消息重试要阻塞后续(或单线程消费);
    • 重试与幂等配合:重投的消息消费时要幂等(去重);
  4. 监控:失败率、重试次数分布、死信量告警。
  5. 容量/重试风暴估算:假设峰值消费 2000 msg/s,其中 5% 下游异常 → 100 msg/s 进入重试;若策略是“立即无限重试”,等效把失败流量以乘数放大,可能在秒级打满下游与线程池。合理预算:最大重试 3~5 次、指数退避(1s/2s/4s)、重试 topic 与主 topic 隔离。线程池:重试消费与主消费隔离,重试线程数 ≤ 下游可承受并发的一定比例(如 20%)。观察指标:失败率、重试队列 lag、下游 RT/错误率。
  6. 失败与降级:识别不可重试异常(参数错误、业务拒绝)→ 直接死信/失败回调,不再重试;可重试异常(超时、限流、短暂不可用)→ 退避重试;达到上限 → 死信 + 告警 + 补偿任务。业务侧开关:极端时可暂停该 topic 消费,先保下游。原则:重试是放大器,必须限次、限流、可熔断。

【原理溯源】

  • 无限重试为什么会堵死队列? 顺序消费/单线程下,毒消息失败后原地重试,后续消息无法前进;即使多线程,持续重试也占用消费容量并放大下游压力。重试解决的是瞬时故障,不是永久错误——不分类就必然踩坑。

  • 为什么要指数退避? 下游故障恢复需要时间。固定短间隔会在下游最脆弱时持续打击(重试风暴)。指数退避 + 抖动能拉开间隔,给恢复窗口,降低惊群。

  • 为什么要有熔断/暂停消费? 当失败率飙升,说明下游整体不可用,此时“继续消费继续失败”毫无意义,还占用资源。暂停消费(消息留在 Broker)是背压:把压力挡在 MQ 外,恢复后再追。比死磕重试安全。

  • Kafka 为什么重试要更“手工”? 不提交 offset 即重投,但会阻塞该分区后续消息;更常见是失败转发到重试 topic(带延迟),主 topic 继续。RocketMQ 把重试队列做成产品能力,Kafka 交给应用。

  • 重试与顺序、幂等的三角关系? 保序 → 失败必须阻塞;重试 → 必然可能重复 → 必须幂等;要吞吐 → 不能无限阻塞。设计时三选二的组合决定架构:交易常用“阻塞重试 + 幂等”,日志常用“跳过/死信 + 最终一致”。

  • 元理论对应: 原理 5「不可靠信道上的正确性协议」(题群见《核心考点与元理论》§5.7)

【选型判断树】

text
消费失败
├─ 异常分类
│   ├─ 不可重试(格式/业务非法)→ 立即死信
│   ├─ 可重试(超时/限流/临时)→ 指数退避,上限 3–5 次
│   └─ 下游整体故障(失败率高)→ 熔断暂停消费
├─ 超过上限 → 死信 + 告警
├─ 顺序要求?
│   ├─ 要求保序 → 阻塞重试,不跳过
│   └─ 无顺序 → 可异步重试 topic
└─ 全程幂等

口诀:先分类再重试;退避防风暴;熔断防雪崩;死信保通道。

【口述骨架】(5 分钟;框架 D,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“重试策略核心是异常分类,而不是次数”
0:30–2:30分类与参数可重试/不可重试;次数、指数退避
2:30–3:30三家实现Rocket 16 次、Rabbit nack、Kafka 不提交/重试 topic
3:30–4:30三大坑重试风暴、乱序、与幂等配合
4:30–5:00收尾“重试是补偿,不是正确性来源;正确性靠幂等与死信闭环”

【关键数字】

参数经验值说明
业务重试上限3–5 次之后死信
RocketMQ 默认16 次,等级递增可改
退避1s,2s,4s… 可加抖动防惊群
熔断阈值失败率 >50% 持续 N 秒按下游 SLA
重试 topic 延迟10s–5min给下游恢复时间
失败重试流量峰值×失败率2000×5%=100 msg/s
重试次数(消息消费失败)3–5 次 + 死信无限重试=事故

【追问链】(三层)

L1|“下游持续故障怎么办?” → 熔断暂停消费,消息在 MQ 保留;恢复后限流爬坡继续。不要无限重试打下游。

L2|“顺序消息重试怎么不乱序?” → 关键是重试不能把消息扔回公共池与其它消息竞争。要点四条:① 按 key/队列串行重试(RocketMQ 顺序消息用本地阻塞重试 + 挂起当前队列,而不是异步重投);② 重试期间该 key 的后续消息不得先行处理,越过失败者就是乱序;③ 达到重试上限才转死信,并保留原始位点(topic/queue/offset)以便按序回放;④ 消费端仍要做状态机或版本号校验,把晚到的旧消息识别并丢弃,作为最后兜底。

L3|“重试次数和延迟怎么定?” → 参考下游恢复时间分布:多数秒级故障用 3 次指数退避覆盖;分钟级依赖故障靠熔断而不是更长重试。重试总时长应 < 业务可接受延迟,超过就进死信并告警。

【评分标准】

档位答案特征
60 分知道要设置最大重试次数
80 分区分可重试/不可重试;指数退避;进死信
95 分谈重试风暴与熔断;顺序阻塞;与幂等联动;给监控指标

【关联题】

【自测】

  1. 消息格式错误为什么要直接进死信? 参考答案: 永久性失败,重试无意义且浪费资源、可能阻塞通道。
  2. 指数退避解决什么问题? 参考答案: 避免在下游故障期高频重试造成重试风暴,给恢复时间。
  3. 判断对错:重试次数越多越可靠。 参考答案: 错。过多重试放大负载、延迟暴露问题;应分类 + 上限 + 死信 + 告警。

91. 核心链路依赖的 MQ 突然不可用(MQ 故障降级) ​

【考察内容】依赖故障的容灾设计

【题目】MQ 集群故障不可用,而核心下单链路依赖它做异步化。业务不能停,怎么办?有没有降级方案(开关切同步、本地暂存、DB 轮询兜底)?恢复后积压数据怎么处理?

【参考答案】

  1. 认知:MQ 是核心依赖,要先做高可用(集群、副本、监控)降低挂的概率;但必须准备“MQ 挂掉”的降级预案;
  2. 降级方案:
    • 本地消息表兜底:业务与消息同库同事务写入本地消息表,MQ 恢复后定时任务把积压消息补发(消息表是“永不丢失”的持久层)——最稳妥;
    • 同步调用降级:MQ 不可用时,异步链路降级为同步调用(如通知/积分直接同步执行,牺牲 RT 保正确性)——适合低频轻量操作;
    • 服务降级:非核心功能(推荐、统计)直接关闭,核心链路(下单、支付)优先保障;
    • 消息落本地文件/磁盘队列:轻量场景可写本地队列文件,恢复后重放(有丢失风险);
  3. 恢复流程:MQ 恢复后,先补发积压(本地消息表/重放),再恢复消费,监控积压量回落;
  4. 预案要求:降级方案要提前演练(混沌工程),不能事发时临场设计。
  5. 容量估算(降级通道):MQ 全挂时,若下单峰值 5000 TPS、每单平均 2 条异步事件,则本地消息表要承接 1 万行/秒 写入——这通常不现实,必须配合:① 只保留核心事件(订单状态),非核心关闭;② 本地表批量写入;③ 业务限流到 DB/本地表可承受范围(如 500–1000 TPS)。口径必须连着说清,否则自相矛盾:题干峰值 5000 TPS 限到 1000 即只放行 20%,所以【关键数字】里「降级期间 ≥99%」只能指被放行的那部分流量的成功率,其余是排队或明确拒绝;不能同时承诺「全量下单 99% 成功」和「限流 1000 TPS」。降级窗口内用户侧表现:主流程可下单,积分/通知延迟;恢复后补发速度按下游容量限流,避免二次打挂。
  6. 失败与降级:预案分三级——① MQ 部分节点故障:自动切其他 broker/集群;② 集群整体不可用:核心链路切本地消息表/事务性 outbox,异步功能降级关闭;③ 长时间不可用:人工确认哪些副作用可丢、哪些必须对账补齐。生产端发送超时要 fail-fast 并写本地,不能让线程池阻塞在 MQ 客户端。演练:定期做 MQ 摘除演练,验证本地表与补发任务。

【原理溯源】

  • 为什么“核心下单”不能同步依赖 MQ? 同步依赖会把 MQ 的可用性直接乘进下单成功率。MQ 再高可用也是额外故障域。正确架构:主事务成功与否只取决于 DB/支付,MQ 只承载可异步、可补偿的副作用。

  • 本地消息表为什么是最佳兜底? 它把“要发的消息”写进与业务同一个本地事务,业务成功则消息记录必然存在。MQ 挂了只是“投递暂停”,不是“消息丢失”。恢复后扫表补发,正确性由数据库事务保证(见 93 题)。

  • 为什么“切同步”要慎用? 同步化会把下游 RT 与错误率拉进主链路,MQ 故障时可能从“异步延迟”变成“下单超时”。只适合低频、轻量、高可用的下游;高 QPS 时应关闭非核心而非全部同步。

  • 恢复期为什么要限流补发? 故障期间堆积的发送/消费会在恢复瞬间形成洪峰,可能再次打挂 MQ 或下游。正确顺序:验证 MQ 健康 → 按速率补发生产 → 消费端限流爬坡 → 观察 lag 与错误率。

  • 降级开关为什么必须提前演练? 事发时没有时间设计与验证。开关粒度(按功能/按接口)、自动触发条件(MQ 健康检查失败 N 次)、回滚条件都要预置。混沌工程定期注入 MQ 故障,验证下单成功率。

  • 元理论对应: 原理 2「过载动力学与雪崩正反馈」+原理 5「不可靠信道上的正确性协议」(题群见《核心考点与元理论》§2.7+§5.7)

【选型判断树】

text
MQ 不可用
├─ 下单主链路是否依赖 MQ 才能成功?
│   ├─ 是(架构问题)→ 立即改:主事务不依赖 MQ
│   └─ 否 → 启动降级
├─ 降级动作
│   ├─ 非核心异步(推荐/统计)→ 直接关闭
│   ├─ 核心副作用(积分/通知)→ 本地消息表暂存,恢复补发
│   └─ 低频关键 → 评估切同步
└─ 恢复
    ├─ 补发限流爬坡
    ├─ 对账确认
    └─ 演练归档

口诀:主链路不绑 MQ;消息表保正确;恢复要限速。

【口述骨架】(5 分钟;框架 D,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“先保证主链路不依赖 MQ 同步可用,再谈降级”
0:30–2:30降级矩阵本地消息表、切同步、关非核心、本地暂存
2:30–3:30恢复流程补发顺序与限流爬坡
3:30–4:30预案工程开关、监控、混沌演练
4:30–5:00收尾“容灾不是加机器,是失败路径可走”

【关键数字】

参数经验值说明
健康检查失败阈值连续 3–5 次触发降级开关
补发速率恢复稳态的 20%→100%防二次洪峰
本地消息表保留至少覆盖最大故障窗口如 7 天
演练频率季度/半年混沌工程
下单成功率目标降级期间 ≥99% 核心路径口径=被放行的那部分流量;与下一行「限流到 500–1000 TPS」不能同时承诺「全量下单 99% 成功」
降级写入预算峰值事件×核心比例5000×2→限流到500–1000 TPS
补发限流≤ 下游容量防恢复后二次故障

【追问链】(三层)

L1|“MQ 挂了订单会不会丢单?” → 若主事务不依赖 MQ,订单在 DB,不丢。异步副作用进本地消息表,恢复后补发。若设计上“不发消息就不能提交订单”,那是架构缺陷,必须改。

L2|“本地消息表会不会把数据库拖垮?” → 会,这正是降级方案最容易忽略的一点。本参考答案第 5 条算过:MQ 全挂时若下单峰值 5000 TPS、每单 2 条异步事件,本地消息表就要承接 1 万行/秒的写入,远超单库能力。所以降级必须同时做三件事:① 只保留核心事件(订单状态变更),非核心直接关闭;② 批量合并写入,攒 100~500 条一次 insert;③ 把业务限流到本地表能承受的量级(500~1000 TPS),宁可排队也不能压垮 DB。恢复后按 (status, 时间) 索引分批补发并限速爬坡,避免补发本身形成二次洪峰。

L3|“如何自动发现 MQ 故障并自动降级?” → 生产探活(发送心跳消息/检查集群状态)+ 错误率熔断。连续失败达到阈值自动切降级开关,并告警。恢复后自动或人工切回。开关要灰度,避免抖动来回切。

【评分标准】

档位答案特征
60 分说“多部署节点做高可用”
80 分给出本地消息表/切同步/关非核心降级
95 分强调主链路不依赖 MQ;恢复限流;预案演练与自动开关

【关联题】

【自测】

  1. MQ 完全不可用时,本地消息表如何保证消息不丢? 参考答案: 消息与业务同一本地事务落库;MQ 只是投递通道,恢复后定时任务扫“待发送”补发。
  2. 为什么不适合把所有异步都改成同步降级? 参考答案: 同步会把下游 RT/故障引入主链路,高并发下可能导致下单超时雪崩;应只保留轻量低频,其余暂存或关闭。
  3. 判断对错:降级方案上线前不用演练,真挂了再按文档操作。 参考答案: 错。未演练的预案不可信;需混沌工程验证开关、补发、回切。

92. “先发消息还是先写库”才能两全(RocketMQ 事务消息) ​

【考察内容】事务消息是 RocketMQ 标志性能力,分布式事务专题高频题

【题目】下单要“写订单库 + 发消息给下游”,先写库可能消息没发出去,先发消息可能库没写成。RocketMQ 事务消息怎么解决这个两难?半消息、本地事务、回查机制分别是什么?

【参考答案】

  1. 解决的问题:先发消息再提交事务(事务失败消息白发了)/先提交事务再发消息(消息发送失败业务成功但下游不知道)——本地操作与发消息无法原子;
  2. 半消息机制:
    • 生产者发送“半消息”(half message)——Broker 收到但不投递给消费者;
    • 执行本地事务(如订单落库);
    • 根据本地事务结果向 Broker 提交 commit(消息可见可投递)或 rollback(删除消息);
  3. 事务状态未知兜底(关键):若生产者本地事务执行中挂了/超时,Broker 长期持有半消息——Broker 会回查(check)生产者(回调 checkLocalTransaction),生产者查询本地事务状态后回复 commit/rollback;
  4. 保证:半消息+提交/回滚+回查,使“本地事务成功⇔消息最终可见”达到最终一致;
  5. 与本地消息表对比:事务消息把“消息表”搬到 MQ 内部,业务少维护一张表,但回查逻辑仍需业务实现。
  6. 容量估算:事务消息比普通消息多一次半消息写入 + 状态回查,吞吐通常低于普通发送(经验值约为普通 sync 的 50%–80%,视回查比例)。若交易峰值需发送 5000 msg/s 事务消息,按 80% 效率折算为 5000÷0.8=6250、按 50% 折算为 10000,故集群容量要按 6250–10000 普通消息当量预估。回查压力:本地事务未决比例越高,回查 QPS 越高;状态表要有索引且查询足够快(按 transactionId/msId)。半消息在 Commit 前对消费者不可见,会增加端到端延迟约一次确认往返(通常毫秒~十毫秒级)。
  7. 失败与降级:半消息发送成功但本地事务失败 → Broker 回查,业务返回回滚,半消息丢弃;本地事务成功但确认消息失败 → Broker 定时回查,业务返回提交,消息最终投递;回查服务不可用 → 依赖事务状态表 + 超时策略,极端时告警人工。若 RocketMQ 不可用 → 降级本地消息表。禁止:无状态表导致回查悬挂/误提交。

【原理溯源】

  • 为什么“先写库”和“先发消息”都不对? 两者不在同一事务域,中间任一崩溃都会造成:库成功消息丢,或消息在库回滚后变成脏消息。这是分布式写“无法两阶段原子”的根本问题,必须引入暂存 + 二次确认或本地同事务记录。

  • 半消息解决了什么? 把消息变成“对消费者不可见的预写”。只有 commit 后才投递,rollback 则删除。这样“发送”动作可以发生在本地事务之前而不产生脏消息。

  • 回查为什么是关键? 生产者可能在本地事务后、commit 前崩溃,Broker 若一直等就永久悬挂半消息。回查让 Broker 主动问“你本地事务到底成没成”,生产者查库答 commit/rollback。没有回查,事务消息就只是“两阶段的一半”。

  • 与本地消息表的同构性? 本质同一思想:把“待发消息”与业务绑定在同一一致性边界。消息表在业务库,由扫表投递;事务消息在 MQ,由回查确认。前者兼容所有 MQ,后者依赖 RocketMQ 能力但少维护一张表。

  • 消费端失败与事务消息无关。 事务消息只保证“生产端本地事务与消息可见”的一致;消费失败仍走重试/死信/幂等。不要混为一谈。

  • 元理论对应: 原理 5「不可靠信道上的正确性协议」(题群见《核心考点与元理论》§5.7)

【选型判断树】

text
如何保证“业务+发消息”原子?
├─ MQ 是否支持事务消息(RocketMQ)?
│   ├─ 是 → 半消息 + 本地事务 + 回查
│   └─ 否 → 本地消息表(Kafka/Rabbit 通用)
├─ 是否强一致到消费完成?
│   └─ 否(最终一致即可)→ 上述方案足够
│   └─ 是 → 还需消费侧对账/TCC,事务消息不够
└─ 回查必须实现:查本地库真实状态,不可写死 commit

口诀:半消息暂存,回查兜底,消费另算。

【口述骨架】(5 分钟;框架 D,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“两难本质是本地事务与发消息跨系统,需要暂存+确认”
0:30–2:30时序半消息 → 本地事务 → commit/rollback → 回查
2:30–3:30为什么一致每一步失败都有路径:rollback 删消息;未知靠回查
3:30–4:30对比与边界vs 本地消息表;消费失败另论
4:30–5:00收尾“事务消息解决的是生产端一致,不是端到端 exactly-once”

【关键数字】

参数经验值说明
回查次数transactionCheckMax 默认 15 次耗尽即回滚半消息
回查间隔检查服务默认每 60s 一轮(transactionCheckInterval),固定周期、不按档位递增「10s→30s→1m→…→2h」是消费重试的延迟档位,别当成回查节奏
半消息对消费者不可见commit 后才投递
本地事务超时秒级完成为佳过长占用半消息
适用一致性最终一致非消费端强一致
事务消息吞吐约为普通 sync 的 50%–80%含半消息与回查开销
回查 RT毫秒~十毫秒依赖状态表查询性能
端到端延迟多一次确认/回查窗口通常仍远小于同步跨服务事务

【追问链】(三层)

L1|“本地事务成功但 commit 消息丢了怎么办?” → Broker 超时后回查,生产者查库发现成功则回 commit,消息最终投递。这就是回查存在的意义。

L2|“回查时生产者服务也挂了呢?” → 半消息会一直停在「未提交」状态,Broker 按 transactionCheckMax(默认 15 次)周期性回查;生产者始终不在线、回查次数耗尽时,Broker 默认回滚这条半消息——宁可不发一条本地事务状态未知的消息。由此有三个必做配套:① 回查接口要幂等且真能查到本地事务状态(按 transactionId/msId 建索引,别在回查里做重活);② 生产者重启后要能立刻恢复回查能力;③ 业务侧必须有对账兜底——「本地事务已提交但消息被回滚」只能靠对账发现并补发,不能指望 MQ 自己纠正。

L3|“事务消息和本地消息表怎么选?” → 有 RocketMQ 且团队接受其 API/运维 → 事务消息,少维护表;需要多 MQ 统一、或已有消息表基建 → 本地消息表。正确性等价,工程取舍不同。

【评分标准】

档位答案特征
60 分知道有半消息
80 分完整时序:半消息→本地事务→提交/回滚
95 分讲透回查;对比本地消息表;说明消费端不在保证范围

【关联题】

【自测】

  1. 半消息与普通消息的区别? 参考答案: Broker 暂存且不对消费者投递,直到 commit;rollback 则删除。
  2. 回查机制解决什么故障? 参考答案: 生产者在本地事务后未能发送 commit/rollback 时,Broker 主动查询本地事务状态以完成提交或回滚。
  3. 判断对错:用了事务消息,消费端就不会重复消费。 参考答案: 错。事务消息只管生产端一致性;消费仍可能重复,需幂等。

93. 不用事务消息,怎么保证“业务和发消息”不丢(本地消息表) ​

【考察内容】本地消息表是分布式事务最经典落地方案

【题目】如果 MQ 不支持事务消息,怎么保证“本地业务操作”和“发消息”要么都成功要么都补偿?本地消息表方案怎么设计?定时任务怎么扫描投递?重复投递怎么幂等?

【参考答案】

  1. 核心思想:业务操作与消息记录在同一个本地事务中完成,消息不丢;投递失败可重试;
  2. 流程:
    • 生产者:开启本地事务 → 执行业务操作(如订单表插入)→ 同时写消息表(msg 状态=待发送)→ 提交事务(原子保证);
    • 发送服务:扫描消息表中“待发送”消息(定时任务)→ 投递 MQ → 收到 ack 后更新状态=已发送;
    • 消费者:消费消息处理业务(幂等),处理成功返回 ack;
  3. 可靠性保证:
    • 不丢:消息与业务同事务落库,投递失败/宕机后定时任务继续扫发(最终一定发出);
    • 不重:消费者幂等(唯一键)+ 发送端状态机(已发送不重发,或重发但消费幂等);
  4. 缺点:每套业务要多维护消息表、扫表有延迟、消息表与业务表耦合;与 RocketMQ 事务消息是同一思路的两种实现(一个在业务侧、一个在 MQ 侧);
  5. 适用:无事务消息功能的 MQ(Kafka/RabbitMQ)场景,或已有消息表基础设施。
  6. 容量估算:本地消息表扫描线程按轮询间隔 T 与批量大小 B 设计。若峰值需投递 2000 msg/s,轮询间隔 500ms,则每批需投递约 1000 条——批量发送比逐条快,但单批不宜过大(避免长时间事务与内存)。表增长:日订单 50 万×平均 2 消息 = 100 万行/日;保留 7 天 ≈ 700 万行,要按时间分区/归档。扫描索引:(status, next_retry_time) 必须有,否则全表扫描会拖垮 DB。
  7. 失败与降级:MQ 暂不可用 → 消息停留在待发送,任务退避重试,不影响业务主事务;消息表所在库压力大 → 批量发送、异步扫描、读写分离;补发风暴 → 限流发送;业务与消息不一致(极少数)→ 对账任务按业务 ID 比对,缺失则补偿发送,多发靠消费幂等。监控:待发送积压量、最老消息年龄、发送失败率。

【原理溯源】

  • 原子性从哪来? 唯一来源是数据库本地事务。把业务行和消息行放进同一事务,要么都提交要么都回滚,消除了“业务成功但消息没记下”的窗口。这比“先调 MQ 再写库”或反之都强。

  • 为什么还要扫表投递? 事务只保证“消息已记录”,不保证“已送到 MQ”。投递是网络 IO,可能失败。扫表把投递变成可重试的最终动作:状态=待发送就继续发,直到 ack。

  • 状态机为什么重要? 待发送→发送中→已发送→(可选)已确认。防止并发扫表重复发送;发送中需超时回退,避免卡死。多实例扫表要用分布式锁或按 id 分片。

  • 重复投递为什么必然存在? 发送成功但更新状态前崩溃 → 下次扫到仍会再发。所以消费端必须幂等——与 MQ at-least-once 同一逻辑,不能假设“发一次”。

  • 与事务消息对比。 同构:半消息 ≈ 待发送行;回查 ≈ 扫表。事务消息把复杂度收到 MQ 内部;消息表通用但要自己维护表、任务、清理。

  • 元理论对应: 原理 5「不可靠信道上的正确性协议」(题群见《核心考点与元理论》§5.7)

【选型判断树】

text
MQ 无事务消息
└─ 本地消息表
    ├─ 同事务写业务+消息行
    ├─ 扫表投递 + 状态机
    ├─ 消费幂等
    └─ 清理已成功消息(TTL/归档)
有 RocketMQ 且愿意绑定
└─ 事务消息(92 题)

口诀:同库事务保不丢,扫表保投递,幂等保不重。

【口述骨架】(5 分钟;框架 D,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“本地消息表用数据库事务绑定业务与待发消息,再扫表投递”
0:30–2:30流程写表 → 扫描投递 → ack 更新状态 → 消费幂等
2:30–3:30为什么可靠原子性来自同事务;最终发出来自扫表
3:30–4:30工程细节状态机、锁、清理、延迟
4:30–5:00收尾“与事务消息同构,是通用底座”

【关键数字】

参数经验值说明
扫表间隔1s–1min延迟 vs 压力
分页大小100–500防大事务
发送中锁超时30s–2min卡死回退
已发送保留1–7 天再清理/归档
消费幂等键业务唯一键非 msgId
批量投递峰值×轮询间隔2000/s×0.5s≈1000条/批
消息表增长日订单×均消息数50万×2=100万行/日
关键索引(status, next_retry_time)无索引会全表扫

【追问链】(三层)

L1|“把发消息写在事务外会怎样?” → 事务提交后、发消息前崩溃,消息永久丢失。必须同事务写消息表。

L2|“多个实例同时扫表会不会重复发?” → 若每个实例都各自扫同一批 status=待发送 的行,就会重复发。防重靠「抢占」而不是「各扫各的」:① UPDATE ... SET status='处理中', owner=?, version=version+1 WHERE status='待发送' LIMIT n(或 SELECT ... FOR UPDATE SKIP LOCKED)先抢到再发;② 或把消息表按 id % N 静态分片给不同实例,互不重叠;③ 接收端仍要保持幂等(消息 ID/业务键唯一索引),因为整条链路仍是至少一次投递。另外扫表要限批量并走 (status, 时间) 索引,否则消息表本身会变成慢查询源。

L3|“消息表越来越大怎么办?” → 定期删除/归档已成功且超过保留期的数据;大表可分区;扫表务必走索引(status + 时间)。

【评分标准】

档位答案特征
60 分知道要有个消息表
80 分同事务写入 + 扫表投递 + 状态机
95 分讲清原子性来源、重复投递必然、清理与锁;能对比事务消息

【关联题】

【自测】

  1. 本地消息表的原子性靠什么保证? 参考答案: 业务数据与消息记录在同一数据库本地事务中提交。
  2. 为什么消费端仍要幂等? 参考答案: 扫表可能重复投递,MQ 也可能重复,必须保证业务效果只发生一次。
  3. 判断对错:本地消息表可以完全替代对账。 参考答案: 错。消费失败、业务部分成功等仍需对账兜底。

94. 一条消息 ack 没配对,消费者重启后消息全丢了(ack 机制) ​

【考察内容】MQ 投递语义体系

【题目】有同事把消费者的 auto-ack 打开后,消费者一处理完(甚至还没处理完)消息就被确认,重启后消息丢失。请讲清生产端 ack 与消费端 ack 的机制,ack 时机配错分别会带来什么问题?

【参考答案】

  1. 生产端 ack(Broker 确认收到):
    • Kafka:acks 参数——acks=0 不等确认(可能丢);acks=1 leader 落盘即确认(leader 挂丢数据);acks=all 等所有 ISR 副本确认(最可靠,延迟最高);
    • RabbitMQ:publisher-confirm,Broker 落盘后回调 ack,未确认/nack 可重发;
    • 意义:生产端确认“消息已持久化”,避免“发了就当成功”导致丢失;
  2. 消费端 ack(消费者确认处理完):
    • 自动 ack:收到消息即 ack——若处理中崩溃,消息已确认=丢失。它默认是开的,安全场景必须显式关掉(RabbitMQ auto_ack=false),日志/统计类才可容忍;
    • 手动 ack:处理成功才 ack;处理失败不 ack(消息重新投递);
    • Kafka:offset 提交机制(enable.auto.commit=false,处理成功后手动提交);
  3. ack 丢失场景:
    • 生产 ack 丢失:消息实际已到 Broker 但生产者没收到 ack → 重发 → 重复消息(消费端幂等解决);
    • 消费 ack 丢失:处理成功但 ack 没送达 → 消息重投 → 重复消费(幂等解决);
  4. 结论:ack 机制解决“丢”与“不丢”,但引入了“重复”——所以幂等是 MQ 可靠性闭环的最后一块拼图。
  5. 容量/确认开销估算:手动 ack 会让消费 RT 从“拉取即可”变成“处理完才确认”。若单条业务处理 10ms、单线程,则上限 100 msg/s/线程;要扛 5000 msg/s 需要 50 线程或 50 分区并行。同步刷盘/acks=all 会增加生产与 Broker 开销,吞吐下降但更安全。缓冲:in-flight 消息过多会导致 Rebalance 时重复面变大;建议限制 max.poll.records 与处理批量,使单次 poll 在会话超时内处理完。
  6. 失败与降级:处理失败不 ack,依赖重投;多次失败进死信;消费者重启后从 last committed offset 继续,可能重复不会丢;若错误地 auto-ack/先 commit 后处理,则崩溃窗口会丢消息——这是本题要消灭的 bug。监控:commit offset 与 end offset 的 lag、消费失败率、死信量。发布时注意 graceful shutdown:处理完 in-flight 再退出,减少重复。

【原理溯源】

  • ack 的本质是什么? 分布式中的进度确认。生产 ack = “Broker 已持久化”;消费 ack = “业务已处理”。两者确认的对象不同,配错会系统性丢消息。

  • auto-ack 为什么会丢? 确认发生在“收到”而非“处理成功”。崩溃窗口在收到与处理完成之间,消息却被视为已消费。手动 ack 把确认点移到副作用之后,崩溃则重投。

  • Kafka acks=all 为什么更安全? 等待 ISR 中所有副本写入后再向生产者确认,leader 故障时数据仍在其他副本。acks=1 只等 leader,acks=0 不等——性能递增,安全性递减。

  • 为什么 ack 可能带来重复? 确认信号本身会丢。生产端超时重发、消费端未收到 ack 结果的重投,都是“不确定就重试”。at-least-once 由此而来。

  • 幂等为什么是最后一块拼图? 不丢(确认尽量晚)与不重(确认尽量早)互斥。系统选择不丢,重复必须由消费幂等吸收,否则“不丢”换来的重复会破坏业务正确性。

  • 元理论对应: 原理 5「不可靠信道上的正确性协议」(题群见《核心考点与元理论》§5.7)

【选型判断树】

text
ack 怎么配?
├─ 消息丢失可容忍?(日志/统计)
│   ├─ 是 → 可 auto-ack + acks=1,换吞吐
│   └─ 否 → 手动 ack + 处理后提交 + acks=all
├─ 生产端
│   ├─ 核心业务 → sync + confirm/acks=all + 重试
│   └─ 高吞吐日志 → 可异步 + 批量
└─ 无论哪种,消费端尽量幂等

口诀:确认点在副作用之后才不丢;不丢必可能重。

【口述骨架】(5 分钟;框架 D,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“ack 分生产与消费两端,配错分别导致丢或重”
0:30–2:30两端机制Kafka acks 三档;confirm;auto vs manual ack
2:30–3:30因果链早 ack→丢;晚 ack→重;重复靠幂等
3:30–4:30事故复盘式本题 auto-ack 导致重启丢失的机理
4:30–5:00收尾“ack 是可靠性开关,不是细节”

【关键数字】

参数经验值说明
Kafka acks0/1/allall 最安全
enable.auto.commit生产建议 false手动提交
autoAck核心业务 falseRabbitMQ
重复率网络抖动时可见必须幂等
ISR副本同步集合acks=all 等待对象
单线程消费上限1/单条RT10ms→100 msg/s
并行需求峰值÷单线程能力5000/100=50 并行(分区/线程)
确认时机业务成功后auto-ack 仅日志类可容忍

【追问链】(三层)

L1|“生产 ack 和消费 ack 分别确认什么?” → 生产:Broker 是否持久化;消费:业务是否处理完成。两端都要正确配置。

L2|“acks=all 会慢多少?” → 慢在多等一轮「ISR 副本把消息拉走并回报」的往返(正常写路径不含选主;副本跟不上被踢出 ISR、或 broker 宕机才走控制器/选举那条路):生产端延迟通常从本地写盘的亚毫秒级涨到几毫秒到几十毫秒。但吞吐不一定同比例掉——Kafka 靠批量摊薄这次等待:linger.ms/batch.size 开起来后,acks=all 相比 acks=1 的吞吐一般只降 10%~30%;若逐条同步发送则会掉一个数量级。所以结论是:开批量、确认幂等生产者未被显式关掉(3.0 起默认已开)、保持足够大的 ISR,核心业务用 acks=all(再配 min.insync.replicas=2、unclean.leader.election.enable=false)这个代价是值得的。

L3|“offset 提交和 ack 是什么关系?” → Kafka 中提交 offset 即消费进度确认。应处理成功后再 commit;auto-commit 定时提交可能在处理中提交,导致崩溃丢消息——与 auto-ack 同类错误。

【评分标准】

档位答案特征
60 分知道不要 auto-ack
80 分分清两端 ack;Kafka 三档;手动提交
95 分讲清“早确认丢、晚确认重”;幂等闭环;offset≈ack

【关联题】

【自测】

  1. auto-ack 在什么时点确认? 参考答案: 收到消息时(或按配置极早确认),处理中崩溃即丢失。
  2. Kafka acks=1 与 all 的差别? 参考答案: 1 只等 leader;all 等全部 ISR 副本,更安全更慢。
  3. 判断对错:ack 越早系统越高效,应尽量早确认。 参考答案: 错。过早确认会丢消息。应在业务成功后确认,用幂等处理重复。

95. 消息每秒 1000 条,下游接口只扛得住 100(消费端流控) ​

【考察内容】消费端容量治理

【题目】消费端每处理一条消息要调一个下游接口,该接口每秒最多承受 100 次调用;但消息流入量每秒 1000 条。怎么设计消费端(限速、分批聚合、削峰填谷、背压)才能既不丢消息又不压垮下游?

【参考答案】

  1. 需求本质:消费速率必须与下游处理能力匹配,防止把下游打挂、防止自己 OOM;
  2. 方案:
    • 限流消费:消费端加限流器(Semaphore/Guava RateLimiter),按下游阈值控制处理速率;
    • 批量消费:一次拉取/确认多条(Kafka 批量拉取、RabbitMQ prefetch 设置),批量处理(如攒 100 条调一次下游批量接口);
    • 控制并发:消费者并发数(线程数/实例数)与下游容量匹配,不盲目扩;
    • 背压/暂停:下游 RT 升高或错误率上升时,熔断暂停消费(消息留在 MQ),恢复后继续——比“继续消费硬重试”安全;
    • 预取限制:RabbitMQ 设 prefetch count(如 1~10),避免一次性拉太多到本地内存;
  3. 监控:消费 lag、处理耗时、下游错误率联动告警;
  4. 特殊场景:大促削峰——允许积压(MQ 天然缓冲),下游恢复后追平,但要设积压告警上限。
  5. 容量估算(步进):上游 1000 msg/s,下游仅 100 QPS,则缺口 900 msg/s 积压;若持续 1 小时,积压 ≈ 900×3600 = 324 万条。按消息体 1KB、副本 3,约 9.7GB。消化策略:① 消费端限流 100/s(示例按下游名义阈值取整;生产要按第 6 条压测阈值的 70%–80% 即 ≈80/s,则缺口 920 msg/s、积压更大)——积压线性涨,需扩下游——但「既不丢又不垮」不能默认成立:MQ 的缓冲是有界的,恒定 900/s 缺口下 1 天就是 7776 万条,按 1KB=1000B、3 副本 ≈ 233GB/日(与同题 L1、【关键数字】的 9.7GB/小时 ×24 对齐),超过消息保留期或磁盘水位就会丢最老消息。要能给出「可容忍积压时长 = min(消息保留期, 可用磁盘 ÷ 日增量)」,超时必须合并处理/扩下游/按分级丢弃非核心,并把该时长本身做成告警阈值;② 下游扩容到 500/s 后积压降速;③ 非核心消息丢弃/合并(如同订单多条事件合并为 1 条)。批量消费:下游若支持批量接口,按每批 100 条 × 10 次/s = 等效 1000 条/s,正好吃掉 1000 msg/s 的流入,且只占 100 QPS 预算的 10%,其余留给重试。RPS 预算:重试流量要计入,避免 100 QPS 被重试吃掉。
  6. 失败与降级:限流阈值动态调整(下游 RT/错误率联动);下游过载 → 熔断消费或降低并发;积压超阈值 → 告警并启动扩容/丢弃非核心;禁止“不限流冲垮下游”。降级目标排序:资金/订单状态 > 通知 > 统计埋点。恢复时爬坡放量。

【原理溯源】

  • 为什么不能“有多少消费多少”? 消费速率若超过下游容量,结果是:下游过载雪崩、错误率暴涨、重试风暴,最终比“主动限速”更慢更糟。流控是保护下游以保总吞吐。

  • 积压为什么反而是正确状态? 在下游容量固定时,多出来的 900/s 必须存在某处。MQ 的磁盘缓冲就是为此设计——允许 lag 上涨,好过打挂下游。这是削峰填谷的消费侧镜像。

  • 批量为什么有效? 把 N 条消息合并为一次下游调用(若下游支持批量接口),或至少摊薄固定开销。1000/s 单条调用改为 10 次×100 条批量,下游压力与 RT 都下降。

  • 背压与限流的区别? 限流是稳态按阈值控制;背压是动态在下游异常时暂停。两者叠加:平时 RateLimiter 100/s,错误率升高时熔断暂停。

  • prefetch 为什么重要? 预取过大 → 本地堆积 → 内存压力,且崩溃时未 ack 的在途消息要整批重投(重复面变大、恢复变慢);过小 → 吞吐不足。按处理时延与并发调优,本质是“在途消息上限”。

  • 元理论对应: 原理 5「不可靠信道上的正确性协议」(题群见《核心考点与元理论》§5.7)

【选型判断树】

text
生产 1000/s,下游 100/s
├─ 下游能否批量接口?
│   ├─ 能 → 聚合批量,提升有效吞吐
│   └─ 不能 → 硬限流 100/s,接受积压
├─ 积压是否可接受?
│   ├─ 可 → MQ 缓冲 + 告警阈值
│   └─ 不可 → 与业务谈降级(丢非核心/合并/近实时采样)
└─ 保护
    ├─ RateLimiter / 信号量
    ├─ 熔断背压
    └─ prefetch 控制在途

口诀:速率对齐下游;多余流量留给 MQ;异常时背压。

【口述骨架】(5 分钟;框架 B,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“消费速率必须 ≤ 下游容量,差额由 MQ 积压吸收”
0:30–2:30组合拳限流、批量、并发控制、prefetch
2:30–3:30背压下游异常时暂停而非硬重试
3:30–4:30积压策略可接受积压 + 告警;大促场景
4:30–5:00收尾“流控是保护下游,不是限制自己”

【关键数字】

参数经验值说明
下游限流按其压测阈值 80%留余量
Rabbit prefetch1–10 起步按 RT 调
批量大小下游批量接口上限内如 50–200
lag 告警持续上涨 5–10min或绝对阈值
熔断条件错误率/RT 双指标联动
积压速率上游−下游1000−100=900 msg/s
1小时积压缺口×3600≈324万条,约9.7GB(1KB×3副本)
等效扩容批量大小×每秒调用次数100条/批×10次/s=等效1000条/s

【追问链】(三层)

L1|“暂停消费会不会丢消息?” → 在缓冲的边界内不会:消息在 Broker 持久化,恢复后继续。但积压是有上界的——按消息保留期与磁盘水位算,恒定缺口(如 900/s ≈233GB/日,该数已含 3 副本,按 1KB=1000B;再乘副本就是重复计算)超过保留期或写满磁盘就会丢最老消息。所以要说「在容忍时长内不丢」,并给出该时长=min(保留期, 可用磁盘÷日增量);同时消费端内存不要无限预取。

L2|“积压越滚越多怎么办?” → RocketMQ/Kafka 都可在消费端设置并发与拉取速率;更常见的是业务层限流:信号量/令牌桶限制同时调用下游的数量,配合批量接口降低有效 RPS。扩容下游时按 20%→50%→100% 爬坡。若下游是第三方(短信/支付),严格按对方限流协议配置,并预留重试预算(通常只用对方配额的 70%–80%)。

L3|“如何动态感知下游容量变化?” → 自适应限流:根据下游 RT/错误率调节并发与速率(类似 TCP 拥塞控制)。或配置中心热更新阈值。核心是把下游健康信号接进消费调度。

【评分标准】

档位答案特征
60 分说“加机器消费”
80 分限流 + 批量 + 控制并发
95 分背压熔断、prefetch、「允许积压」要带边界(积压有界:保留期/磁盘水位+容忍时长,超限必须扩下游或按分级丢弃)、动态调参

【关联题】

【自测】

  1. 为什么 1000/s 进、100/s 出时不应该全速消费? 参考答案: 会打挂下游并引发重试风暴;应限流到下游容量,积压留在 MQ。
  2. 批量消费的前提是什么? 参考答案: 下游支持批量或单次调用固定开销可被摊薄;且业务允许微批延迟。
  3. 判断对错:消费端 prefetch 越大吞吐越高越好。 参考答案: 错。过大导致内存压力与故障恢复变慢,应按处理能力设置。

96. MQ 上线后要盯哪些指标,才能提前发现风险(MQ 监控) ​

【考察内容】MQ 可观测性

【题目】MQ 接入核心链路后,运维要建立监控大盘。要盯哪些指标才能提前发现风险(积压、消费停滞、broker 负载、延迟)?哪些指标出现什么趋势就说明要出事了?

【参考答案】

  1. 积压量(Lag):消费 offset 落后生产 offset 多少——积压持续上涨=消费能力不足或下游故障,是最核心指标;按 topic/分区维度监控;
  2. 消费速率与生产速率:对比判断是否失衡;
  3. 消费失败率:失败率高=业务异常/消息异常,联动重试与死信监控;
  4. 死信队列量:死信持续产生=有系统性不可恢复问题,告警人工介入;
  5. 吞吐与延迟:生产/消费 TPS、端到端延迟(生产到消费的耗时)、单条处理耗时——分开看才能区分「在排队」与「处理慢」;
  6. Broker 健康:CPU、磁盘(分区文件增长)、内存、网络、副本同步状态(ISR 收缩=副本异常,数据安全风险)、消费组 Rebalance 次数与时长;
  7. 重试次数分布:大量消息频繁重试=下游不稳定;
  8. 告警设计:积压量超过阈值(如 10 万)告警;失败率突增告警;死信非零告警;磁盘水位 75% 预警、85% 严重(与第 9 条同口径)。
  9. 容量相关监控阈值示例:生产 TPS 突增 > 基线 3 倍告警;消费 lag > 10 万或持续上涨 5 分钟告警;单 broker 磁盘水位 > 75% 预警、> 85% 严重;副本同步延迟 > 5s 关注;生产失败率 > 0.1% 告警;死信增长速率突变告警。容量巡检:按周评估峰值 TPS vs 集群水位,提前扩容分区/broker。大盘应同时展示:流入、流出、lag、失败、磁盘、CPU——只看 TPS 会漏掉“在堆积”和“在失败”。
  10. 失败与降级:监控本身挂了要有备用(多采集通道/云监控);告警要分级(P0 电话/P1 IM/P2 日报),避免告警疲劳;预设应急 runbook:积压怎么扩容、MQ 挂了怎么切本地消息表、磁盘满怎么处理。演练:季度故障演练验证监控能否在 5 分钟内发现问题。没有 runbook 的告警只是噪音。

【原理溯源】

  • 为什么 Lag 是第一指标? 它是生产与消费速率差的积分,直接对应“业务还要等多久”。Lag 持续上涨 = 系统在积累延迟债;Lag 为 0 但失败率高 = 消息可能在“假成功”。

  • 为什么要同时看速率与 Lag? 只看 Lag 会误判:短暂峰值 Lag 涨但 P>C 已恢复会回落;若 C 长期 < P,Lag 线性上涨。速率对比能区分“瞬时”与“趋势”。

  • 死信为什么必须非零告警? 死信是“最终处理不了”的信号。若只监控消费成功量,可能 lag 正常但业务数据已丢。死信与失败率是质量指标,lag 是进度指标。

  • ISR 收缩为什么危险? 同步副本减少,acks=all 可用性下降,leader 故障时丢数据或不可用风险上升。这是数据安全的前置信号,应在丢消息之前发现。

  • 端到端延迟与处理耗时的区别? 端到端含排队(积压);处理耗时是单条业务逻辑。前者高可能是积压,后者高是代码/下游慢——处置完全不同。

  • 监控的层次? ① 进度(lag、速率);② 质量(失败、死信、重试);③ 资源(CPU、磁盘、网络、ISR)。缺一层就有盲区。

  • 元理论对应: 原理 5「不可靠信道上的正确性协议」(题群见《核心考点与元理论》§5.7)

【选型判断树】

text
监控大盘最小集
├─ 进度:生产 TPS、消费 TPS、Lag(topic/分区)
├─ 质量:失败率、重试分布、死信量
├─ 时延:端到端延迟、单条处理耗时
└─ 资源:Broker CPU/磁盘/网络、ISR、副本滞后

告警分级
├─ P0:死信非 0、ISR 收缩、磁盘 >85%、消费停滞
├─ P1:Lag 持续上涨、失败率突增
└─ P2:处理耗时缓慢劣化

口诀:进度质量资源三层都要;lag 为 0 未必安全。

【口述骨架】(5 分钟;框架 D,骨架共性见文首第二节,本题只写特定内容)

时间任务内容
0:00–0:30定性“监控分进度、质量、资源三层,Lag 最核心但不够”
0:30–2:30指标清单Lag、速率、失败、死信、延迟、Broker、ISR
2:30–3:30趋势解读哪些组合意味着要出事
3:30–4:30告警设计阈值与分级
4:30–5:00收尾“监控是为了在用户投诉前发现”

【关键数字】

参数经验值说明
Lag 告警绝对值(如 >10 万)或持续上涨 5–10min按业务 SLA 调
磁盘水位75% 预警 / 85% 严重预留清理时间,配合保留策略
死信非 0 告警人工介入
ISR收缩即告警数据安全
消费失败率突增或 >1%–5%看基线(正文第 3 条)
端到端延迟超 SLA 告警如 >30s
生产失败率>0.1% 告警确认链路是否静默丢(正文第 9 条)
TPS 突增>基线3倍可能大促或异常重试

【追问链】(三层)

L1|“Lag 为 0 就安全吗?” → 不一定。可能消费失败进死信、或消息被错误丢弃、或根本没生产。要结合生产量、失败率、死信一起看。

L2|“如何区分消费慢和下游挂了?” → 先把第 5 条的「端到端延迟(含排队)」与「单条处理耗时」拆开看。端到端涨但单条耗时正常=消息在排队,是消费能力不足;单条耗时本身涨,才往下游查。第二步看质量指标:消费失败率(第 3 条)与重试次数分布(第 7 条「大量消息频繁重试=下游不稳定」)一起飙升、错误类型集中在超时/连接拒绝,就是下游故障;只有耗时高、失败率平,是消费自己慢(线程池、GC、慢 SQL)。第三步排除中间件本身:ISR 收缩、磁盘水位、Rebalance 次数(第 6 条)异常时问题在 MQ。三者对应的处置完全不同——扩分区与消费者、熔断降级保护下游、修 Broker。

L3|“大盘之外还要什么?” → 链路追踪(msgId 贯穿)、审计(谁发的什么)、容量预测(磁盘增长、分区水位)、混沌演练验证告警有效性。监控是可观测性的一部分,不是全部。

【评分标准】

档位答案特征
60 分只提积压量
80 分Lag + 失败率 + Broker 资源
95 分进度/质量/资源三层;ISR 与死信;趋势解读;lag=0 的陷阱

【关联题】

【自测】

  1. 为什么死信量要单独告警? 参考答案: 死信代表最终处理失败,可能造成业务数据丢失;仅看 lag 会漏掉“消费成功但进死信”的假阴性。
  2. ISR 收缩意味着什么? 参考答案: 同步副本减少,数据可靠性下降,leader 故障时可能丢消息或不可用,需立即处理。
  3. 判断对错:只要消费 TPS 高就说明系统健康。 参考答案: 错。可能伴随高失败率或大量重试;需结合质量指标。

持续学习,持续积累。