Springboot使用Rabbitmq的延時隊列+死信隊列實現(xiàn)消息延期消費
簡介
RabbitMQ 的延時隊列(Delayed Queue)是指消息在發(fā)送到隊列后,會在一定的時間內(nèi)被延遲處理,直到預(yù)設(shè)的延遲時間結(jié)束,消息才會被消費者消費。RabbitMQ 本身并不原生支持延時隊列,但我們可以通過一些插件或特定配置來實現(xiàn)這種功能。
延時隊列的應(yīng)用場景
- 定時任務(wù):比如定時發(fā)送郵件、推送通知等操作。
- 重試機制:當某些任務(wù)失敗時,可以讓任務(wù)在一段時間后再次嘗試。
- 延遲消費:在特定時間后消費消息,而不是立即消費。
死信隊列(Dead Letter Queue,簡稱 DLQ)是消息隊列中的一個特殊隊列,用于存放那些因某些原因無法被正常消費的消息。死信隊列在許多消息隊列系統(tǒng)(如 RabbitMQ、Kafka 等)中都有應(yīng)用。通過將無法消費的消息轉(zhuǎn)移到死信隊列中,可以幫助開發(fā)者發(fā)現(xiàn)和處理消息消費過程中的異常或錯誤。
死信隊列的主要用途是:
- 錯誤處理:用于捕獲無法消費的消息,避免這些消息丟失,并提供后續(xù)人工干預(yù)或自動重試的機會。
- 監(jiān)控和診斷:通過查看死信隊列,可以幫助開發(fā)者快速發(fā)現(xiàn)問題,例如隊列被溢出、消息格式不正確等。
- 防止數(shù)據(jù)丟失:將那些因為超時、隊列溢出、消費失敗等原因無法處理的消息放入死信隊列,可以保證它們不會丟失,后續(xù)可以進一步調(diào)查和處理。
本文結(jié)合延時隊列和死信隊列,延時隊列的消息設(shè)置過期時間,在時間過期之后,將消息內(nèi)容推送到死信隊列中進行處理。
整合邏輯
依賴
<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)建交換機|隊列
延時隊列
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 {
// 交換機
@Bean
public DirectExchange orderDelayExchange(){
return new DirectExchange(RabbitMqConfigEnum.ORDER_DELAY.getExchange());
}
// 隊列
@Bean
public Queue orderDelayQueue(){
Map<String,Object> args = new HashMap<>();
// 延時隊列,需要綁定死信隊列,在ttl到期之后,將隊列轉(zhuǎn)到死信隊列
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());
}
}死信隊列
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 {
// 交換機
@Bean
public DirectExchange orderDeadExchange(){
return new DirectExchange(RabbitMqConfigEnum.ORDER_DEAD.getExchange());
}
// 隊列
@Bean
public Queue orderDeadQueue(){
return QueueBuilder.durable(RabbitMqConfigEnum.ORDER_DEAD.getQueue()).autoDelete().build();
}
// 交換機、隊列、路由鍵綁定
@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","訂單延時隊列","order_delay_exchange", "order_delay_queue", "order_delay_routing_key",true),
ORDER_DEAD("direct","訂單死信隊列","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: 無返回結(jié)果的手動處理方式
*/
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ù)返回的布爾類型,進行確認和拒絕
*/
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);
}
}
// 確認消息
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("消息消費失敗,消息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;
}
});
}
}案例
實體類
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ā)送消息時間{}",new Date());
rabbitmqService.sendDelayMsg(RabbitMqConfigEnum.ORDER_DELAY,orderInfo,10000);
}
}消費者監(jiān)聽消息
消費者接口
package com.walker.rabbitmq.delay.service;
public interface IRabbitCosumerHandler {
Boolean handle(String message);
}消費者監(jiān)聽方法
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("接受消息時間 {}",new Date());
String msg = new String(message.getBody());
log.info("當前時間:{},隊列{}收到消息:{}", 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("進行訂單取消的操作");
return true;
}
}到此這篇關(guān)于Springboot使用Rabbitmq的延時隊列+死信隊列實現(xiàn)消息延期消費 的文章就介紹到這了,更多相關(guān)Springboot使用Rabbitmq消息延期消費 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解在Spring?Boot中使用數(shù)據(jù)庫事務(wù)
本篇文章主要介紹了詳解在Spring?Boot中使用數(shù)據(jù)庫事務(wù),具有一定的參考價值,感興趣的小伙伴們可以參考一下<BR>2017-05-05
maven pom中內(nèi)置變量及引用的實現(xiàn)
maven其實有很多內(nèi)置變量供開發(fā)著在開發(fā)中使用,本文主要介紹了maven pom中內(nèi)置變量及引用的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下2024-01-01
IDEA使用Mybatis插件 MyBatisCodeHelper-Pro的圖文教程
這篇文章主要介紹了IDEA使用Mybatis插件 MyBatisCodeHelper-Pro的教程,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09
Spring Boot Actuator監(jiān)控端點小結(jié)
這篇文章主要介紹了Spring Boot Actuator監(jiān)控端點小結(jié),需要的朋友可以參考下2017-06-06

