Skip to content

消息队列如何保证消息不丢失? ​

  • 标签:消息队列、后端

🏛 业务架构师 ​

消息不丢失要在三段链路各自设防,丢的位置决定防护动作:

生产端(网络丢、发送失败)

  • 确认机制:RabbitMQ 用 confirm + return 回调;Kafka 设置 acks=all(全部 ISR 副本确认才算成功)+ retries;
  • 发送失败要落地:重试超限后写本地表/死信,由补偿任务转投——不能只打日志。

Broker(宕机、刷盘丢)

  • 持久化:RabbitMQ 队列与消息都 durable;Kafka 副本数 ≥2、min.insync.replicas≥2、关闭 unclean 选举;
  • 集群化部署,单机 broker 的持久化承诺是有限的。

消费端(处理了但没 ack / ack 了但没处理)

  • 手动 ack,处理成功后再确认(自动 ack = 处理前确认,挂了就丢);
  • 但要防"处理失败无限重试":有限重试 + 死信队列 + 人工兜底。

架构视角的三点判断:

  1. 可靠性是有价格的:acks=all + 同步刷盘,吞吐降数倍——按消息价值分级配置:订单消息全防护,埋点日志尽力而为。一套集群全按最高等级配,是用钱换心理安慰;
  2. "不丢失"的最后防线是对账:所有防护叠加后仍有极小概率缝隙(极端故障组合),T+1 对账 + 差异自动修复是业务闭环的真正终点——金融场景没有"靠 MQ 保障资金一致"这种说法;
  3. 端到端 trace:消息 ID 全链路透传,丢了能定位丢在哪段——不可观测的可靠性等于不可验证。

一句话:生产确认、存储持久、消费手动 ack、对账兜底——四道闸门按业务价值选择性投资。

🏗 系统架构师 ​

消息不丢失要在生产、存储、消费三段分别保证,链路上任何一段断了都前功尽弃:

生产端(防发送丢失):

  • 同步发送或异步带回调确认;Kafka 配 acks=all(等待所有 ISR 副本落盘)+ retries 重试 + 发送缓冲区失败兜底日志; -RocketMQ 用同步发送 + SendResult 状态检查;本地消息表兜底:发失败落库,定时任务补发——重要业务我必配这个;
  • RabbitMQ 开 publisher confirm + mandatory + 备份交换机。

Broker(防存储丢失):

  • Kafka:replication.factor >= 3、min.insync.replicas >= 2,禁用 unclean leader 选举(非 ISR 副本不能当 leader);
  • RocketMQ 同步刷盘(SYNC_FLUSH)+ 主从同步复制(非异步);
  • 单机 Raid/多副本,不迷信单盘。

消费端(防消费丢失):

  • 手动 ack,处理成功后才确认;禁止自动提交 offset(自动提交 = 拿到就确认,处理一半宕机即丢);
  • 消费失败重试 + 死信队列,杜绝无限重试阻塞;
  • Kafka 手动提交 offset + 消费幂等(at-least-once 交付会重复,用幂等收敛到 exactly-once 效果)。

验证闭环:对账系统是最终防线——生产/消费计数对账,差异告警补数。架构认知:exactly-once 是昂贵的,at-least-once + 幂等消费是性价比最高的工程组合。

💻 全栈开发专家 ​

消息不丢失 = 三段全设防:生产端、Broker、消费端。任何一段裸奔都白搭。

生产端(别发出去了就算完):

java
// RabbitMQ:confirm 模式,broker 确认落盘
rabbitTemplate.setConfirmCallback((data, ack, cause) -> {
    if (!ack) resend(data);               // 失败重投 + 本地记录
});
// Kafka:acks=all + 重试
props.put("acks", "all");                 // 等所有 ISR 副本确认
props.put("retries", 3);
props.put("enable.idempotence", true);    // 重试不产生重复

关键:发送前先落业务库/本地消息表,发送失败可追溯重发,别让消息只活在内存里。

Broker 端:

  • RabbitMQ:队列 durable=true,消息 deliveryMode=2 持久化;
  • Kafka:replication.factor>=3、min.insync.replicas=2(acks=all 才有意义)、关闭 unclean 选举。

消费端(处理成功才确认):

java
// 关闭自动 ack,业务落库成功后手动确认
channel.basicConsume(queue, false, new DefaultConsumer(channel) {
    public void handleDelivery(...) {
        saveToDb(body);
        channel.basicAck(tag, false);   // 失败 nack 重回队列/死信
    }
});

注意自动重试的死循环:毒消息反复失败会堵塞队列,要配最大重试次数 + 死信队列。

兜底闭环:消息表 + 定时对账(发出未确认/消费未回执的消息补发),我做对账系统时发现它能兜住 1% 的边角案例。三段设防 + 对账,才敢说"不丢"。