跳到主要内容

消息可靠性

消息可靠性涵盖发布、保存、消费和进度确认。本文以 Kafka 4.1 与 RabbitMQ 4.1 为例,负责各阶段确认和失败恢复。

1. 面试核心​

先记住这几条,再读正文
  • 发布与消费确认。 RabbitMQ publisher confirm 确认发布阶段,consumer ack 确认消费阶段。卡 · 综合题
  • 不可路由消息。 mandatory 发布的不可路由消息,会先 basic.return 再 publisher confirm。卡 · 生产者确认
  • 副本确认。 Kafka 的 acks=all 等待 ISR 确认,min.insync.replicas 限定最低副本数。卡 · 生产者确认
  • 消费进度。 消费完成后再确认,Kafka 只推进连续完成范围的 offset。卡 · 消费者确认时机
  • 重试与死信。 延迟重试保留原消息 ID,死信存放终止重试的失败消息。

2. 生产者确认​

RabbitMQ publisher confirm 与 consumer ack 相互独立。可路由消息被所有目标队列接收后,broker 才确认发布。持久消息进入持久队列时,还涉及持久化确认。quorum queue 的确认要求副本多数接受。

不可路由消息也会得到 publisher confirm。发布时设置 mandatory,可接收 basic.return。客户端应同时处理返回消息和确认结果。

Kafka 的 acks=all 等待当前 ISR 满足确认条件。配合 min.insync.replicas=2,可要求至少两个同步副本。ISR 数量不足时,写入返回副本不足错误。

副本数为 3、ISR 为 3 时,acks=all 等待 ISR 中全部副本。min.insync.replicas=2 并非只等待任意两个。ISR 收缩为 1 时,写入失败而非降为单副本成功。

enable.idempotence=true 去重生产者协议重试。Kafka 4.1 要求 acks=all、retries 大于 0。还要求 max.in.flight.requests.per.connection<=5。应用再次主动发送同一业务消息,仍需业务 ID 去重。

业务提交与消息发布的双写窗口可用 Outbox 处理。数据库事务同时保存业务变化和待发布事件。后台发布失败后,根据事件记录继续尝试。

3. 消费者确认时机​

先提交业务,再确认消费进度,可避免提前跳过处理。业务完成后、确认前崩溃会重复,处理方式见消息幂等性。

Kafka committed offset 表示恢复后要读取的下一位置。假设 10、11、12 三条记录并行处理。10 和 12 已完成,11 尚未完成,此时最多提交 11。提交 13 会让重启后跳过 11。

采用手动完成追踪时,设置 enable.auto.commit=false。应用逐分区维护连续完成位置,再提交 offset。

RabbitMQ delivery tag 只在当前 Channel 内有效。消费者应在接收消息的 Channel 上确认。multiple=true 会确认该 tag 及之前的未确认投递。因此并发任务也需防止确认越过未完成项。

prefetch 限制未确认投递数量。增大 prefetch 可提高在途并发,也增加内存与故障重投量。

4. 即时重试​

短暂连接故障可进行有限重试,并使用退避和抖动。重试必须服从总处理时限。参数或 schema 错误应进入可观察的失败路径。

立即 requeue 会让失败消息快速重投。持续循环会占满消费者,并反复冲击下游。

5. 延迟重试​

长等待可进入 retry topic 或 retry queue。消息保留原 ID,并记录次数和下次执行时间。例如按 10 秒、1 分钟、10 分钟分层重试。

Kafka retry topic 本身不会按时间自动延迟交付。消费端或调度组件需要检查到期时间。RabbitMQ 可用 TTL 配合 DLX 实现等待队列。单条消息 TTL 混用长短延迟时,会出现队头等待。

将失败消息移入重试通道,会改变原处理顺序。顺序敏感业务需决定是否暂停同键后续消息。

6. 死信队列​

RabbitMQ 配置 DLX 后,可转发被拒绝且不 requeue 的消息。消息过期、队列长度限制也可触发死信。quorum queue 超过 delivery-limit 时同样可进入死信。

应用重试次数超限,需要显式转入对应失败路径。Kafka 的死信 topic 由应用或框架实现。

死信应保留原消息 ID、业务键、来源和错误类别。另记录尝试次数、首次与最后失败时间。修复后需要限速重放,并保留审计结果。

7. 死信转发失败​

默认 dead-lettering 以 at-most-once 转发。转发到目标失败时,消息存在丢失窗口。只配置 DLX 尚缺少转发确认保障。

RabbitMQ 4.1 的源 quorum queue 可启用以下策略:

  • dead-letter-strategy=at-least-once。
  • overflow=reject-publish。
  • 配置 dead-letter-exchange 和有效路由。
  • 确认 stream_queue feature flag 已启用。

源队列保留消息,直到内部转发获得确认。目标故障时消息继续占用源队列容量。因此还需限制容量并监控积压。目标收到重复消息时,仍需幂等处理。

转发会保留原消息的 delivery mode。目标重启后仍需保存消息时,应使用持久目标队列。源发布消息也应设置持久化。

8. 重放​

重放保留原始消息 ID,以便消费端去重。另增加执行批次标识,用于跟踪本次恢复。

开始前核对旧消息的版本和消费逻辑。重放速率受下游剩余容量约束。顺序敏感数据按键处理,并支持暂停和断点续跑。

9. 端到端指标​

分别记录发布失败、路由失败和副本状态。消费侧观察 lag、未确认量、失败率和最老消息年龄。重试与死信通道也要监控转发和目标增量。

关键链路通过事件 ID 对照最终业务结果。broker 接收成功只完成中间一段交付。

10. 常见问题​

10.1 发布确认与路由结果​

publisher confirm 可确认不可路由消息。mandatory 与 basic.return 用于暴露这类失败。之后的消费完成由 consumer ack 单独表达。

10.2 立即重新入队​

11 · 面试题卡片​

11.1 如何设计消息确认、重试和死信流程​

出现公司:Shopee、京东

题型:综合题

考察重点

  • 生产确认与消费确认的独立边界。
  • 副作用提交和 ack、offset 提交的顺序。
  • 延迟重试、死信、重放和端到端核对。

相关内容:生产者确认至端到端指标。

参考回答

发布确认、消费确认和业务提交是独立阶段。

RabbitMQ 同时检查 publisher confirm 与路由返回。Kafka 使用 acks=all 并配置足够的同步副本。

业务提交后再确认,重复由业务幂等处理。重试分为有限即时重试和延迟重试。永久失败进入死信,转发、告警和限速重放也要覆盖。最终用消息 ID 核对业务结果。

11.2 RabbitMQ 发布确认成功,为什么目标队列仍没有消息?​

出现公司:暂无已核验的公开来源(本站自测)

考察重点:publisher confirm 与 mandatory 返回。

相关内容:生产者确认。

参考回答

不可路由消息也可获得 publisher confirm。

mandatory=true 时,broker 会先发 basic.return。客户端需要处理路由返回,再结合发布确认判断结果。确认发布阶段也并未确认消费者业务完成。

11.3 副本数为 3,acks=all、min.insync.replicas=2 如何确认写入?​

出现公司:暂无已核验的公开来源(本站自测)

考察重点:ISR 全部确认与最低同步副本数。

相关内容:生产者确认。

参考回答

ISR 有 3 个副本时,acks=all 等待这 3 个副本确认。

ISR 为 2 时等待这 2 个。ISR 只有 1 个时,低于 min.insync.replicas=2,写入失败。它以写入可用性换取最低副本保障。

11.4 Kafka 的 offset 10、12 完成而 11 未完成,能提交 13 吗?​

出现公司:暂无已核验的公开来源(本站自测)

考察重点:下一读取位置与连续完成水位。

相关内容:消费者确认时机。

参考回答

应最多提交 11,让恢复从未完成的记录继续。

提交 13 会跳过 11。并行消费要逐分区追踪连续完成位置。12 在故障恢复后可再次到达,因此业务仍需幂等。

11.5 RabbitMQ quorum queue 怎样提高死信转发可靠性?​

出现公司:暂无已核验的公开来源(本站自测)

考察重点:转发确认、源端保留与容量代价。

相关内容:死信转发失败。

参考回答

使用至少一次死信转发,让源队列保留消息直到目标确认。

配置 dead-letter-strategy=at-least-once 和 overflow=reject-publish,并设置 DLX 与有效路由。还需确认 stream_queue feature flag 已启用。目标故障时,未确认的消息会占用源队列容量,因此需要监控积压。

12. 参考资料​