Springboot使用Rabbitmq的延時(shí)隊(duì)列+死信隊(duì)列實(shí)現(xiàn)消息延期消費(fèi)
簡(jiǎn)介
RabbitMQ 的延時(shí)隊(duì)列(Delayed Queue)是指消息在發(fā)送到隊(duì)列后,會(huì)在一定的時(shí)間內(nèi)被延遲處理,直到預(yù)設(shè)的延遲時(shí)間結(jié)束,消息才會(huì)被消費(fèi)者消費(fèi)。RabbitMQ 本身并不原生支持延時(shí)隊(duì)列,但我們可以通過(guò)一些插件或特定配置來(lái)實(shí)現(xiàn)這種功能。
延時(shí)隊(duì)列的應(yīng)用場(chǎng)景
- 定時(shí)任務(wù):比如定時(shí)發(fā)送郵件、推送通知等操作。
- 重試機(jī)制:當(dāng)某些任務(wù)失敗時(shí),可以讓任務(wù)在一段時(shí)間后再次嘗試。
- 延遲消費(fèi):在特定時(shí)間后消費(fèi)消息,而不是立即消費(fèi)。
死信隊(duì)列(Dead Letter Queue,簡(jiǎn)稱 DLQ)是消息隊(duì)列中的一個(gè)特殊隊(duì)列,用于存放那些因某些原因無(wú)法被正常消費(fèi)的消息。死信隊(duì)列在許多消息隊(duì)列系統(tǒng)(如 RabbitMQ、Kafka 等)中都有應(yīng)用。通過(guò)將無(wú)法消費(fèi)的消息轉(zhuǎn)移到死信隊(duì)列中,可以幫助開(kāi)發(fā)者發(fā)現(xiàn)和處理消息消費(fèi)過(guò)程中的異常或錯(cuò)誤。
死信隊(duì)列的主要用途是:
- 錯(cuò)誤處理:用于捕獲無(wú)法消費(fèi)的消息,避免這些消息丟失,并提供后續(xù)人工干預(yù)或自動(dòng)重試的機(jī)會(huì)。
- 監(jiān)控和診斷:通過(guò)查看死信隊(duì)列,可以幫助開(kāi)發(fā)者快速發(fā)現(xiàn)問(wèn)題,例如隊(duì)列被溢出、消息格式不正確等。
- 防止數(shù)據(jù)丟失:將那些因?yàn)槌瑫r(shí)、隊(duì)列溢出、消費(fèi)失敗等原因無(wú)法處理的消息放入死信隊(duì)列,可以保證它們不會(huì)丟失,后續(xù)可以進(jìn)一步調(diào)查和處理。
本文結(jié)合延時(shí)隊(duì)列和死信隊(duì)列,延時(shí)隊(duì)列的消息設(shè)置過(guò)期時(shí)間,在時(shí)間過(guò)期之后,將消息內(nèi)容推送到死信隊(duì)列中進(jìn)行處理。
整合邏輯
依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.30</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.27</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> <version>2.7.7</version> </dependency>
配置
server: port: 8081 spring: application: name: walker-rabbitmq rabbitmq: host: 127.0.0.1 port: 5672 username: guest_walker password: guest
創(chuàng)建交換機(jī)|隊(duì)列
延時(shí)隊(duì)列
package com.walker.rabbitmq.delay.config; import com.walker.rabbitmq.delay.constants.BaseConstant; import com.walker.rabbitmq.delay.enums.RabbitMqConfigEnum; import org.springframework.amqp.core.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; @Configuration public class OrderDelayRabbitConfig { // 交換機(jī) @Bean public DirectExchange orderDelayExchange(){ return new DirectExchange(RabbitMqConfigEnum.ORDER_DELAY.getExchange()); } // 隊(duì)列 @Bean public Queue orderDelayQueue(){ Map<String,Object> args = new HashMap<>(); // 延時(shí)隊(duì)列,需要綁定死信隊(duì)列,在ttl到期之后,將隊(duì)列轉(zhuǎn)到死信隊(duì)列 args.put(BaseConstant.xDeadLetterExchange,RabbitMqConfigEnum.ORDER_DEAD.getExchange()); args.put(BaseConstant.xDeadLetterRoutingKey,RabbitMqConfigEnum.ORDER_DEAD.getRoutingKey()); return QueueBuilder.durable(RabbitMqConfigEnum.ORDER_DELAY.getQueue()).autoDelete().withArguments(args).build(); } // @Bean public Binding orderDelayQueueBinding(){ return BindingBuilder.bind(orderDelayQueue()).to(orderDelayExchange()).with(RabbitMqConfigEnum.ORDER_DELAY.getRoutingKey()); } }
死信隊(duì)列
package com.walker.rabbitmq.delay.config; import com.walker.rabbitmq.delay.enums.RabbitMqConfigEnum; import org.springframework.amqp.core.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class OrderDeadRabbitConfig { // 交換機(jī) @Bean public DirectExchange orderDeadExchange(){ return new DirectExchange(RabbitMqConfigEnum.ORDER_DEAD.getExchange()); } // 隊(duì)列 @Bean public Queue orderDeadQueue(){ return QueueBuilder.durable(RabbitMqConfigEnum.ORDER_DEAD.getQueue()).autoDelete().build(); } // 交換機(jī)、隊(duì)列、路由鍵綁定 @Bean public Binding orderDeadQueueBinding(){ return BindingBuilder.bind(orderDeadQueue()).to(orderDeadExchange()) .with(RabbitMqConfigEnum.ORDER_DEAD.getRoutingKey()); } }
常量
package com.walker.rabbitmq.delay.constants; import com.walker.rabbitmq.delay.enums.RabbitMqConfigEnum; public interface BaseConstant { String xDeadLetterExchange = "x-dead-letter-exchange"; String xDeadLetterRoutingKey = "x-dead-letter-routing-key"; }
相關(guān)枚舉
package com.walker.rabbitmq.delay.enums; import lombok.AllArgsConstructor; import lombok.Getter; @AllArgsConstructor @Getter public enum RabbitMqConfigEnum { ORDER_DELAY("direct","訂單延時(shí)隊(duì)列","order_delay_exchange", "order_delay_queue", "order_delay_routing_key",true), ORDER_DEAD("direct","訂單死信隊(duì)列","order_dead_exchange", "order_dead_queue", "order_dead_routing_key",true); private final String type; private final String title; private final String exchange; private final String queue; private final String routingKey; private final Boolean durable; }
rabbitmq工具類封裝
package com.walker.rabbitmq.delay.service; import cn.hutool.core.util.BooleanUtil; import cn.hutool.json.JSONUtil; import com.rabbitmq.client.Channel; import com.walker.rabbitmq.delay.enums.RabbitMqConfigEnum; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.AmqpException; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessagePostProcessor; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.io.IOException; import java.util.function.Consumer; import java.util.function.Function; @Component @Slf4j public class RabbitmqService { @Resource private RabbitTemplate rabbitTemplate; /** * author:walker * time: 2024/12/30 * description: 無(wú)返回結(jié)果的手動(dòng)處理方式 */ public void doWithManualAck(String event, Message message, Channel channel, Consumer<String> method) { String data = new String(message.getBody()); try { method.accept(data); sendAck(message, channel); } catch (Exception e) { log.error("處理事件[{}]異常數(shù)據(jù)為:{},原因如下:",event,data, e); sendNack(message, channel); } } /** * author:walker * time: 2024/12/30 * description: 根據(jù)返回的布爾類型,進(jìn)行確認(rèn)和拒絕 */ public void doWithManualAck(String event, Message message, Channel channel, Function<String,Boolean> method) { String data = new String(message.getBody()); try { Boolean res = method.apply(data); if(BooleanUtil.isTrue(res)){ sendAck(message, channel); }else{ sendNack(message, channel); } } catch (Exception e) { log.error("處理事件[{}]異常數(shù)據(jù)為:{},原因如下:",event,data, e); sendNack(message, channel); } } // 確認(rèn)消息 public void sendAck(Message message, Channel channel) throws IOException { channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } // 拒絕消息 public void sendNack(Message message, Channel channel) { try { channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } catch (IOException e) { log.error("消息消費(fèi)失敗,消息ID:{}", message.getMessageProperties().getMessageId(), e); } } public <T> void sendDelayMsg(RabbitMqConfigEnum configEnum, T data, Integer delayTime) { rabbitTemplate.convertAndSend(configEnum.getExchange(), configEnum.getRoutingKey(), JSONUtil.toJsonStr(data), new MessagePostProcessor() { @Override public Message postProcessMessage(Message message) throws AmqpException { message.getMessageProperties().setExpiration(String.valueOf(delayTime)); return message; } }); } }
案例
實(shí)體類
package com.walker.rabbitmq.delay.entity; import lombok.Data; @Data public class OrderInfo { private String productName; private Integer num; private String userName; private String orderTime; }
controller 創(chuàng)建訂單
package com.walker.rabbitmq.delay.controller; import com.walker.rabbitmq.delay.enums.RabbitMqConfigEnum; import com.walker.rabbitmq.delay.entity.OrderInfo; import com.walker.rabbitmq.delay.service.RabbitmqService; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.Date; @Slf4j @RestController @RequestMapping("/order") public class OrderController { @Resource private RabbitmqService rabbitmqService; /** * author:walker * time: 2025/1/2 * description: 下單 */ @GetMapping("/create") public void createOrder() { OrderInfo orderInfo = new OrderInfo(); orderInfo.setProductName("羽毛球"); orderInfo.setUserName("張三"); orderInfo.setOrderTime("2025-01-02 12:00:00"); orderInfo.setNum(1); log.info("發(fā)送消息時(shí)間{}",new Date()); rabbitmqService.sendDelayMsg(RabbitMqConfigEnum.ORDER_DELAY,orderInfo,10000); } }
消費(fèi)者監(jiān)聽(tīng)消息
消費(fèi)者接口
package com.walker.rabbitmq.delay.service; public interface IRabbitCosumerHandler { Boolean handle(String message); }
消費(fèi)者監(jiān)聽(tīng)方法
package com.walker.rabbitmq.delay.listener; import com.rabbitmq.client.Channel; import com.walker.rabbitmq.delay.enums.RabbitMqConfigEnum; import com.walker.rabbitmq.delay.constants.BaseConstant; import com.walker.rabbitmq.delay.service.IRabbitCosumerHandler; import com.walker.rabbitmq.delay.service.RabbitmqService; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.io.IOException; import java.util.Date; @Slf4j @Component public class OrderDeadLetterQueueConsumer implements IRabbitCosumerHandler { @Resource private RabbitmqService rabbitmqService; @RabbitListener(queues = "#{T(com.walker.rabbitmq.delay.enums.RabbitMqConfigEnum).ORDER_DEAD.getQueue()}",ackMode = "MANUAL") public void receive(Message message, Channel channel) throws IOException { log.info("接受消息時(shí)間 {}",new Date()); String msg = new String(message.getBody()); log.info("當(dāng)前時(shí)間:{},隊(duì)列{}收到消息:{}", new Date().toString(),RabbitMqConfigEnum.ORDER_DEAD.getQueue(), msg); rabbitmqService.doWithManualAck(RabbitMqConfigEnum.ORDER_DEAD.getTitle(), message, channel, this::handle); } @Override public Boolean handle(String message) { // 處理邏輯 log.info("進(jìn)行訂單取消的操作"); return true; } }
到此這篇關(guān)于Springboot使用Rabbitmq的延時(shí)隊(duì)列+死信隊(duì)列實(shí)現(xiàn)消息延期消費(fèi) 的文章就介紹到這了,更多相關(guān)Springboot使用Rabbitmq消息延期消費(fèi) 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java Hibernate多對(duì)多映射詳解及實(shí)例代碼
這篇文章主要介紹了java Hibernate多對(duì)多映射詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-01-01詳解在Spring?Boot中使用數(shù)據(jù)庫(kù)事務(wù)
本篇文章主要介紹了詳解在Spring?Boot中使用數(shù)據(jù)庫(kù)事務(wù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下<BR>2017-05-05maven pom中內(nèi)置變量及引用的實(shí)現(xiàn)
maven其實(shí)有很多內(nèi)置變量供開(kāi)發(fā)著在開(kāi)發(fā)中使用,本文主要介紹了maven pom中內(nèi)置變量及引用的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01IDEA使用Mybatis插件 MyBatisCodeHelper-Pro的圖文教程
這篇文章主要介紹了IDEA使用Mybatis插件 MyBatisCodeHelper-Pro的教程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09java打包maven啟動(dòng)報(bào)錯(cuò)jar中沒(méi)有主清單屬性
本文主要介紹了java打包maven啟動(dòng)報(bào)錯(cuò)jar中沒(méi)有主清單屬性,可能原因是創(chuàng)建springboot項(xiàng)目時(shí),自動(dòng)導(dǎo)入,下面就來(lái)介紹一下解決方法,感興趣的可以了解一下2024-03-03Java自動(dòng)化工具Ant的基礎(chǔ)使用教程
這篇文章主要介紹了Java自動(dòng)化工具Ant的基礎(chǔ)使用教程,例子在Windows系統(tǒng)下操作演示,講解了Ant基本的文件操作和屬性,需要的朋友可以參考下2016-02-02Spring Boot Actuator監(jiān)控端點(diǎn)小結(jié)
這篇文章主要介紹了Spring Boot Actuator監(jiān)控端點(diǎn)小結(jié),需要的朋友可以參考下2017-06-06