跳到主要内容

消息可靠性保障

问题

如何保证消息不丢失?消息从发送到消费的全链路中,有哪些环节可能丢消息?如何分别应对?

答案

消息丢失的三个环节

环节 ① Producer → Broker 发送阶段

MQ保障机制说明
Kafkaacks=all + 重试ISR 全部写入才确认,失败自动重试
RocketMQ同步发送 + 重试send() 同步等待确认,默认重试 2 次
RabbitMQConfirm 机制Broker 确认收到后回调,Nack 时重发
Kafka Producer 可靠配置
Properties props = new Properties();
props.put("acks", "all"); // ISR 全部写入
props.put("retries", 3); // 重试 3 次
props.put("retry.backoff.ms", 1000); // 重试间隔
props.put("enable.idempotence", true); // 开启幂等(自动去重)
props.put("max.in.flight.requests.per.connection", 5); // 幂等模式下最大 5
RocketMQ 同步发送
// 同步发送,等待 Broker 确认
SendResult result = producer.send(msg);
if (result.getSendStatus() != SendStatus.SEND_OK) {
// 发送失败处理
log.error("发送失败: {}", result);
}

环节 ② Broker 存储阶段

MQ刷盘策略副本策略
KafkaPage Cache + 异步刷盘ISR 副本同步(min.insync.replicas=2
RocketMQ同步刷盘 / 异步刷盘同步复制 / 异步复制
RabbitMQ消息持久化镜像队列 / Quorum Queue
RocketMQ Broker 可靠配置
# 同步刷盘(性能低但不丢消息)
flushDiskType=SYNC_FLUSH

# 同步复制(Master 和 Slave 都写入才返回)
brokerRole=SYNC_MASTER
同步刷盘 vs 异步刷盘
  • 异步刷盘(ASYNC_FLUSH):消息写入 Page Cache 就返回,吞吐量高,但宕机可能丢失少量数据
  • 同步刷盘(SYNC_FLUSH):消息写入磁盘才返回,绝对不丢但吞吐量下降约 10 倍

金融级场景用同步刷盘 + 同步复制;一般业务用异步刷盘 + 同步复制即可。

环节 ③ Broker → Consumer 消费阶段

核心原则:先处理业务逻辑,再提交确认(ACK)

MQ正确做法错误做法
Kafka关闭自动提交,处理完手动 commitSync()自动提交 offset 后消费失败
RocketMQ消费成功返回 CONSUME_SUCCESS抛异常未捕获导致 ACK
RabbitMQ关闭自动 ACK,处理完 basicAck()自动 ACK 后消费失败
Kafka 手动提交 Offset
props.put("enable.auto.commit", "false");  // 关闭自动提交

while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
processMessage(record); // 先处理业务
}
consumer.commitSync(); // 处理完再提交
}

三端可靠性配置总结


常见面试问题

Q1: 消息 100% 不丢失可能吗?

答案

理论上无法 100% 保证(极端情况如所有磁盘同时损坏),但可以做到 99.999%+ 的可靠性

  • Producer:Confirm/ACK + 重试 + 本地消息表兜底
  • Broker:同步刷盘 + 多副本同步 + 跨机房容灾
  • Consumer:手动确认 + 失败重试 + 死信队列

最极致的方案是 本地消息表:Producer 写消息前先写本地 DB 事务表,定时扫描未成功的消息补发。

Q2: 自动提交 Offset 有什么问题?

答案

Kafka 的 enable.auto.commit=true 会在 poll() 时自动提交上一批的 offset。

问题场景:

  1. 消费者拉取消息后自动提交了 offset
  2. 消费者在处理消息时崩溃
  3. 重启后从已提交的 offset 开始消费,之前未处理的消息就丢失了

解决:关闭自动提交,处理完业务后手动 commitSync()

Q3: 消费失败怎么办?

答案

  1. 重试:将失败消息重新入队,延迟后重新消费
    • RocketMQ:自动重试 16 次(递增间隔)
    • RabbitMQ:Nack + requeue 或 延迟重试队列
    • Kafka:手动实现重试逻辑
  2. 死信队列:超过最大重试次数,消息进入死信队列
  3. 人工介入:监控死信队列,告警 + 人工排查处理
  4. 补偿机制:定期比对上下游数据,发现不一致时补偿

Q4: Kafka 的 min.insync.replicas 有什么作用?

答案

配合 acks=all 使用,指定 ISR 中最少副本数。

例如 min.insync.replicas=2

  • 当 ISR 中有 ≥ 2 个副本时,消息写入所有 ISR 副本后才确认
  • 当 ISR 中只剩 1 个副本时,Producer 写入会报 NotEnoughReplicasException,拒绝写入

这保证了即使 Leader 宕机,至少还有一个 Follower 有完整数据。

相关链接