尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
面试官:“如何保证RabbitMQ消息不丢失?”全链路排查+实战代码,一篇讲透
很多面试Java后端岗位的人都会遇到这道经典面试题今天我也来讲讲我对这道题的理解。其实面试官并不单单想听你背八股文说出“持久化”、“手动ACK”这些关键词我在面试中直接被问你实际项目中是怎么做的配置怎么配代码怎么写的等这些实际问题。面试官是想知道你是否真正理解消息从生产到消费的完整链路你是否在实际项目中踩过坑、解决过问题你是否能给出系统性的解决方案而不是零散的知识点所以回答这个问题的关键不是简单的堆砌概念而是要展示你的全链路思维。一条消息从生产者发出到被消费者消费需要经历三段路生产者 → 交换机 → 队列 → 消费者每一段都有可能丢失消息对应三个核心问题生产者 → 交换机消息发出去了但是交换机不存在或者路由规则配置错误消息直接丢了而生产者这边却不知道。交换机 → 队列 →Broker存储消息到了Broker但是Broker宕机重启内存里的消息全没了。队列 → 消费者消费者拉取到了消息还没处理完就崩了但默认自动ACK消息已经被删除了。第一关生产者端——让每条消息都有回执如果面试官追问“生产者怎么知道消息有没有发送成功?这时候你要抛出两个核心机制Publisher Confirm和Return Callback。Publisher Confirm消息到达交换机后Broker会给生产者一个确认acktrue或拒绝ackfalse。Return Callback消息到了交换机但是没有匹配到任何队列时Broker会把消息退回给生产者。两者配合就能确保生产者端不会丢失消息。在代码层面先进行配置spring: rabbitmq: publisher-confirm-type: correlated # 开启Confirm4 publisher-returns: true # 开启Return然后配置回调ConfigurationSlf4jpublicclassRabbitConfig{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());// 记录到数据库后续人工处理});}}发送消息时别忘了设置 mandatorytrue否则Return回调不会触发rabbitTemplate.setMandatory(true);rabbitTemplate.convertAndSend(order-exchange,order.create,messageBody,newCorrelationData(UUID.randomUUID().toString()));第二关Broker端——三重持久化一个都不能少面试官追问“如果Broker挂了怎么办”这时候可以告诉面试官Broker要做到消息不丢失需要三样东西同时持久化要素配置交换机durable true队列durable true消息deliveryMode 2BeanpublicQueueorderQueue(){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(queuesorder-queue)publicvoidhandleOrder(Messagemessage,Channelchannel)throwsIOException{longdeliveryTagmessage.getMessageProperties().getDeliveryTag();try{StringbodynewString(message.getBody(),StandardCharsets.UTF_8);OrderorderJSON.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);// 重试次数控制IntegerretryCountmessage.getMessageProperties().getHeader(x-retry-count);intcurrent(retryCountnull)?0:retryCount;if(current3){// 未超限重新入队message.getMessageProperties().setHeader(x-retry-count,current1);channel.basicNack(deliveryTag,false,true);}else{// 超限拒绝消息进入死信队列log.error(重试耗尽转入死信队列);channel.basicNack(deliveryTag,false,false);}}}终极兜底本地消息表面试官如果继续追问“如果Confirm回调本身也丢了呢”这时候我们要说出终极方案——本地消息表核心思路很简单业务数据和消息记录在同一个本地事务中写入数据库。定时任务轮询“待发送”状态的消息调用MQ发送。发送成功后更新为“已发送。如果失败则进入重试超过重试次数标记为“失败”人工介入。Transactional(rollbackForException.class)publicvoidcreateOrderReliably(Orderorder){// 1. 业务入库orderMapper.insert(order);// 2. 消息记录入库同事务MessageLoglognewMessageLog();log.setMsgId(UUID.randomUUID().toString());log.setMsgBody(JSON.toJSONString(order));log.setStatus(PENDING);messageLogMapper.insert(log);}Scheduled(fixedDelay30000)publicvoidretryPendingMessages(){ListMessageLogpendingmessageLogMapper.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笔记 小李版】我们一起进步
RELATED

相关推荐

避坑指南:WordPress主菜单插件最佳实践与选型全解析

避坑指南:WordPress主菜单插件最佳实践与选型全解析

避坑指南:WordPress主菜单插件最佳实践与选型全解析 找建站公司怕被坑高价?很多甲方一上来就问“这个功能多少钱”,结果对方报个天价,或者为了多收费,非要把简单的菜单做成复杂的定制开发。其实,WordPress主菜单插件这块,水没那么深…

📅 2026/9/27 2:44:12
3个代码技巧搞定wordpress评论后刷新怎么选不卡

3个代码技巧搞定wordpress评论后刷新怎么选不卡

3个代码技巧搞定wordpress评论后刷新怎么选不卡 模板网站太丑不够用,这是很多站长接手二开项目时的第一反应。看着现成的主题,配色土气,布局僵硬,想改吧,又怕动了核心逻辑导致后台崩溃。这时候,你往往会被迫去考虑 怎么选…

📅 2026/9/27 2:44:12
A100 NVLink配置实战:驱动安装、fabricmanager与带宽测试全流程

A100 NVLink配置实战:驱动安装、fabricmanager与带宽测试全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/27 2:44:12
MORE NEWS

更多资讯

📰

瑞芯微RV1126B SDK移植实战:从DDR配置到USB调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Visual C++ 14.0 以上版本安装包:解决 Python 包编译失败与运行时缺失

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

公司电商网站开发避坑指南:3步搞定不拖期

公司电商网站开发避坑指南:3步搞定不拖期 改个需求建站公司拖一周,这种憋屈感创业团队负责人太熟悉了。别急着换供应商,先看看这篇保姆级建站教程。我们拆解从0到1的完整链路,帮你把工期压缩一半。 运营目标与指标:别只盯着上线日期…

📰

2026最新网站搭建招标方案避坑指南:3招搞定拖延症

2026最新网站搭建招标方案避坑指南:3招搞定拖延症 改个按钮颜色,建站公司拖一周才给回话?这种“需求黑洞”在河南的中小企业圈子里太常见了。别怪你脾气急,是传统的 网站搭建招标方案…

📰

2026最新做网站都需要什么工具?防坑指南与实战清单

2026最新做网站都需要什么工具?防坑指南与实战清单 找建站公司最怕什么?怕报价单上藏着坑,怕几千块的项目最后变成几万的无底洞,更怕网站上线三天就被挂马,SEO全废。很多SEO从业者接了单子才发现,自己根本不懂底层逻辑,客户一问服务器配置、…

📰

EEG运动想象分类:CNN-Transformer混合架构原理与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬