新闻详情

新闻详情

首页 / 资讯中心 / 详情

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

发布时间:2026/9/28 18:57:06来源:尧图网络
面试官:“如何保证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

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

电压法找断点全攻略:万用表测电压三步锁定线路故障 2026/9/28 18:57:03

电压法找断点全攻略:万用表测电压三步锁定线路故障

电压法找断点,说穿了就是拿万用表在通电线路上量电压,靠电压的有无和大小,反推断点在哪儿。比通断法(蜂鸣档)靠谱得多,因为通断法必须断电,而且线路中间只要串了一个负载,测出来就是…

阅读更多 →
EMC暗室日常使用与维护指南:屏蔽效能、吸波材料与故障排查 2026/9/28 18:57:03

EMC暗室日常使用与维护指南:屏蔽效能、吸波材料与故障排查

做EMC设计这些年,我进暗室的次数比进自己办公室都多。很多工程师把暗室当成一个“大铁柜子”,觉得只要门能关上、里面没灯坏,就万事大吉。直到我见过有人把饮料洒在铁氧体上、有人用错清洁剂擦吸波海绵、还有人在暗室里用普通对讲机直接导致背…

阅读更多 →
格式排版改到崩溃?资深导师力荐这几个AI论文工具 2026/9/28 18:57:03

格式排版改到崩溃?资深导师力荐这几个AI论文工具

写论文最怕的就是格式排版搞得人崩溃,更别提选题、查文献、写大纲这些前期工作了——其实只要用对AI工具、走对流程,效率能翻倍。不少资深教授都建议学生提前布局,用好智能工具来辅助写作。我们实测下来,千笔AI(中文全…

阅读更多 →
电压法定位线路断点:万用表三个前提与三种实战测法 2026/9/28 18:57:03

电压法定位线路断点:万用表三个前提与三种实战测法

1. 引言:断电不一定是真断,断点比你想象中更难找搞维修的人应该都有这种经历:灯具不亮、插座没电,表笔往上一戳,万用表显示“没电压”,于是断定线路断了。可当你沿着线管一路排查,撬开接线盒、拆…

阅读更多 →
Windows 本地 AI 智能体 OpenClaw 部署全解:TaoToken 统一 Key 配置与全部安装报错处理方案 2026/9/28 18:57:03

Windows 本地 AI 智能体 OpenClaw 部署全解:TaoToken 统一 Key 配置与全部安装报错处理方案

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

阅读更多 →
OpenSpec 安装与使用详解:用 TaoToken 统一 Key 打通 AI 工具配置 2026/9/28 18:56:56

OpenSpec 安装与使用详解:用 TaoToken 统一 Key 打通 AI 工具配置

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

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉