news 2026/9/27 2:49:07

面试官:“如何保证RabbitMQ消息不丢失?”全链路排查+实战代码,一篇讲透

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
面试官:“如何保证RabbitMQ消息不丢失?”全链路排查+实战代码,一篇讲透

很多面试Java后端岗位的人都会遇到这道经典面试题,今天我也来讲讲我对这道题的理解。
其实,面试官并不单单想听你背八股文说出“持久化”、“手动ACK”这些关键词;我在面试中直接被问:你实际项目中是怎么做的,配置怎么配,代码怎么写的等这些实际问题。面试官是想知道:

  • 你是否真正理解消息从生产到消费的完整链路?
  • 你是否在实际项目中踩过坑、解决过问题?
  • 你是否能给出系统性的解决方案,而不是零散的知识点?

所以,回答这个问题的关键不是简单的堆砌概念,而是要展示你的全链路思维。

一条消息从生产者发出到被消费者消费,需要经历三段路:
生产者 → 交换机 → 队列 → 消费者,每一段都有可能丢失消息,对应三个核心问题:

  • 生产者 → 交换机:消息发出去了,但是交换机不存在或者路由规则配置错误,消息直接丢了,而生产者这边却不知道。
  • 交换机 → 队列 →Broker存储:消息到了Broker,但是Broker宕机重启,内存里的消息全没了。
  • 队列 → 消费者:消费者拉取到了消息还没处理完就崩了,但默认自动ACK,消息已经被删除了。

第一关:生产者端——让每条消息都有"回执"

如果面试官追问:“生产者怎么知道消息有没有发送成功?"
这时候你要抛出两个核心机制:Publisher Confirm和Return Callback。
Publisher Confirm:消息到达交换机后,Broker会给生产者一个确认(ack=true)或拒绝(ack=false)。
Return Callback:消息到了交换机,但是没有匹配到任何队列时,Broker会把消息退回给生产者。两者配合,就能确保生产者端不会丢失消息。
在代码层面,先进行配置:

spring: rabbitmq: publisher-confirm-type: correlated # 开启Confirm4 publisher-returns: true # 开启Return

然后配置回调:

@Configuration@Slf4jpublicclassRabbitConfig{@AutowiredprivateRabbitTemplaterabbitTemplate;@PostConstructpublicvoidinit(){rabbitTemplate.setConfirmCallback((correlationData,ack,cause)->{if(ack){log.info("消息已经到达交换机,id={}",correlationData.getId());//更新本地消息表状态("已送达")}else{log.info("消息未到达交换机,cause={}",cause);//触发重试或告警}});rabbitTemplate.setReturnsCallback(returnedMessage->{log.error("消息未路由到队列, exchange={}, routingKey={}",returnedMessage.getExchange(),returnedMessage.getRoutingKey());// 记录到数据库,后续人工处理});}}

发送消息时别忘了设置 mandatory=true,否则Return回调不会触发

rabbitTemplate.setMandatory(true);rabbitTemplate.convertAndSend("order-exchange","order.create",messageBody,newCorrelationData(UUID.randomUUID().toString()));

第二关:Broker端——三重持久化,一个都不能少

面试官追问:“如果Broker挂了怎么办?”
这时候可以告诉面试官:Broker要做到消息不丢失,需要三样东西同时持久化

要素配置
交换机durable = true
队列durable = true
消息deliveryMode = 2
@BeanpublicQueueorderQueue(){returnQueueBuilder.durable("order-queue").withArgument("x-dead-letter-exchange","dlx-exchange").withArgument("x-dead-letter-routing-key","dlx-routing-key").build();}

第三关:消费者端——手动ACK + 死信队列

面试官追问:“如果消费者处理到一半突然挂了怎么办?”

这里是最容易丢失消息的环节,也是开发最容易踩坑的地方。默认情况下,消费者是自动ACK的,消息一拉取到,还没处理完,Broker就认为已消费并删除消息,如果此时消费者突然挂了,那么消息就会永久丢失。

解决方案:关闭自动ACK+手动确认

pring:rabbitmq:listener:simple:acknowledge-mode:manual# 关闭自动ACK6prefetch:1# 每次只拉一条,防止消息堆积

消费者代码

@RabbitListener(queues="order-queue")publicvoidhandleOrder(Messagemessage,Channelchannel)throwsIOException{longdeliveryTag=message.getMessageProperties().getDeliveryTag();try{Stringbody=newString(message.getBody(),StandardCharsets.UTF_8);Orderorder=JSON.parseObject(body,Order.class);// 幂等校验:防止重复消费if(isAlreadyProcessed(order.getOrderId())){channel.basicAck(deliveryTag,false);return;}// 执行业务逻辑stockService.deductStock(order.getGoodsId(),order.getQuantity());// 业务成功,手动ACKchannel.basicAck(deliveryTag,false);}catch(Exceptione){log.error("消费失败",e);// 重试次数控制IntegerretryCount=message.getMessageProperties().getHeader("x-retry-count");intcurrent=(retryCount==null)?0:retryCount;if(current<3){// 未超限,重新入队message.getMessageProperties().setHeader("x-retry-count",current+1);channel.basicNack(deliveryTag,false,true);}else{// 超限,拒绝消息,进入死信队列log.error("重试耗尽,转入死信队列");channel.basicNack(deliveryTag,false,false);}}}

终极兜底:本地消息表

面试官如果继续追问:“如果Confirm回调本身也丢了呢?”

这时候我们要说出终极方案——本地消息表,核心思路很简单:

  • 业务数据和消息记录在同一个本地事务中写入数据库。
  • 定时任务轮询“待发送”状态的消息,调用MQ发送。
  • 发送成功后更新为“已发送。
  • 如果失败,则进入重试;超过重试次数标记为“失败”,人工介入。
@Transactional(rollbackFor=Exception.class)publicvoidcreateOrderReliably(Orderorder){// 1. 业务入库orderMapper.insert(order);// 2. 消息记录入库(同事务!)MessageLoglog=newMessageLog();log.setMsgId(UUID.randomUUID().toString());log.setMsgBody(JSON.toJSONString(order));log.setStatus("PENDING");messageLogMapper.insert(log);}@Scheduled(fixedDelay=30000)publicvoidretryPendingMessages(){List<MessageLog>pending=messageLogMapper.selectByStatus("PENDING");for(MessageLogmsg:pending){if(msg.getRetryCount()>=5){messageLogMapper.updateStatus(msg.getMsgId(),"FAILED");continue;}``` rabbitTemplate.convertAndSend("order-exchange","order.create",msg.getMsgBody(),newCorrelationData(msg.getMsgId()));messageLogMapper.incrementRetryCount(msg.getMsgId());}}

本地消息表的作用在于:把“发消息”这个动作从同步调用变成了“异步补偿”,即使MQ短暂不可用,消息也不会丢失。

综上,这个面试题可以作如下回答:

” 消息从生产到消费有三个环节可能丢失,我的方案是全链路闭环:
生产者端: 我开启了Publisher Confirm和Return Callback,确保消息到达交换机,未路由的消息也能被回收处理。对于核心业务,还会配合本地消息表做最终兜底。
Broker端: 我确保交换机、队列、消息三者都做了持久化,生产环境使用集群模式避免单点故障。
消费者端: 我关闭了自动ACK,改为手动确认,业务处理成功后才ACK。处理失败的消息会有限次重试,重试耗尽后转入死信队列,避免消息丢失和毒消息循环。同时业务层做好幂等处理,防止重复消费。
这套方案在我们项目中已经跑了很久,核心业务消息零丢失。“

【Java笔记 小李版】,我们一起进步!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 2:48:56

多平台网站建设报价揭秘:选哪家好,看完这份明细再掏钱

多平台网站建设报价揭秘:选哪家好,看完这份明细再掏钱 网站做好了没人访问,这是很多老板建站后最头疼的事。明明花了几万块,首页设计得挺漂亮,但后台流量几乎为零,咨询更是寥寥无几。这时候你才反应过来,当初选建站公司时,只问了“ 多平台网站建设哪家好 ”,却忽略了更关键的适配性和技术底层。…

作者头像 李华
网站建设 2026/9/27 2:48:22

网站被黑挂马别慌,专业做网站建设公司排名实测,修复到底多少钱

网站被黑挂马别慌,专业做网站建设公司排名实测,修复到底多少钱 你的网站突然弹窗全是色情广告,或者浏览器直接提示“不安全”,这时候你第一反应肯定是:找谁修?修一次到底 多少钱 ?别急着掏钱,很多小白因为不懂行,被不良外包坑了大几千,结果网站还是被黑。…

作者头像 李华
网站建设 2026/9/27 2:47:30

wordpress后台数据库改密码这5个注意事项能救命

wordpress后台数据库改密码这5个注意事项能救命 很多新手接手网站后,第一反应就是去后台改密码,结果一操作就懵了:后台登录页转圈、数据库连接报错、甚至整个站点直接挂掉。这种“备案流程一头雾水”的状态,在改密码环节会变成“技术链路一头雾水”。别慌,这不是你笨,而是你没搞清楚 WordPress…

作者头像 李华
网站建设 2026/9/27 2:47:19

网站建设丿金手指谷哥12实战案例:解决域名服务器难题

网站建设丿金手指谷哥12实战案例:解决域名服务器难题 域名服务器配置是建站第一道坎,很多新手在此卡壳。结合上海创业团队实战案例,我们拆解网站建设丿金手指谷哥12的核心步骤。从需求痛点到技术选型,再到实操部署,全程直击“域名服务器搞不懂”的痛点。 为什么域名服务器配置总出错?…

作者头像 李华
网站建设 2026/9/27 2:47:13

3步搞定国家化妆品备案网官网搭建完整流程

3步搞定国家化妆品备案网官网搭建完整流程 网站被黑挂马不知道怎么办?别慌,这通常是基础安全没做好。很多甲方对接人找上门,第一句话都是“我的备案网站怎么变成博彩广告了”,第二句就是“能不能救”。救是可以救,但预防才是根本。今天咱们不聊虚的,直接拆解 国家化妆品备案网官网 这类高合规性网站的搭建…

作者头像 李华