创建私信队列并绑定
# 死信交换机配置 以直连交换机为列
my:
exchangeNormalName: exchange.normal.a #正常交换机
queueNormalName: queue.normal.a #正常队列
exchangeDlxName: exchange.dlx.a #死信交换机
queueDlxName: queue.dlx.a #死信队列
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* 死信交换机
*/
@Configuration
public class DeadLetterExchangeConfig {
@Value("${my.exchangeNormalName}")
private String exchangeNormalName;
@Value("${my.queueNormalName}")
private String queueNormalName;
@Value("${my.exchangeDlxName}")
private String exchangeDlxName;
@Value("${my.queueDlxName}")
private String queueDlxName;
/**
* 正常交换机
* @return
*/
@Bean
public DirectExchange normalExchange(){
return ExchangeBuilder.directExchange(exchangeNormalName).build();
}
/**
* 正常队列
* @return
*/
@Bean
public Queue normalQueue(){
Map<String, Object> arguments = new HashMap<>();
//重点:设置这两个参数
//设置队列的死信交换机
arguments.put("x-dead-letter-exchange",exchangeDlxName);
//设置死信路由key,要跟死信交换机和死信队列绑定的路由key一致
arguments.put("x-dead-letter-routing-key","error");
return new Queue(queueNormalName,true,false,false,arguments);
}
/**
* 正常交换机和正常队列绑定
* @param normalExchange
* @param normalQueue
* @return
*/
@Bean
public Binding bingNormal(DirectExchange normalExchange,Queue normalQueue){
return BindingBuilder.bind(normalQueue).to(normalExchange).with("order");
}
/**
* 死信交换机
* @return
*/
@Bean
public DirectExchange dlxExchange(){
return ExchangeBuilder.directExchange(exchangeDlxName).build();
}
/**
* 死信队列
* @return
*/
@Bean
public Queue dlxQueue(){
return new Queue(queueDlxName,true,false,false);
}
/**
* 死信交换机和死信队列绑定
* @param dlxExchange
* @param dlxQueue
* @return
*/
@Bean
public Binding bindDlx(DirectExchange dlxExchange,Queue dlxQueue){
return BindingBuilder.bind(dlxQueue).to(dlxExchange).with("error");
}
}
消息发送:
/**
* 死信交换机-直连交换机 消息过期
*/
@Test
public void sendDirectMessage2() {
String messageId = String.valueOf(UUID.randomUUID());
String messageData = "test message, hello!";
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String, Object> map = new HashMap<>();
map.put("messageId", messageId);
map.put("messageData", messageData);
map.put("createTime", createTime);
//给消息设置过期时间
MessagePostProcessor messagePostProcessor = message -> {
//20秒后过期
message.getMessageProperties().setExpiration("20000");
message.getMessageProperties().setContentEncoding("UTF-8");
//持久化
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
//非持久化
//message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
return message;
};
//将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange
rabbitTemplate.convertAndSend("exchange.normal.a", "order", map, messagePostProcessor);
}
适用场景: 偶发性错误(如临时依赖服务不可用),消息可重试。
操作步骤:
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* 死信队列消费 手动重新投递消息
*/
@Component
public class DlxReceiver {
@Autowired
private RabbitTemplate rabbitTemplate;
// 监听死信队列
@RabbitListener(queues = "queue.dlx.a",ackMode = "MANUAL")
public void handleDlqMessage(Map<String,Object> messageBody,Channel channel, Message message) throws IOException {
try {
String originalExchange = getOriginalExchange(message);
String originalRoutingKey = getOriginalRoutingKey(message);
// 重新发布到原队列的交换机
rabbitTemplate.convertAndSend(
originalExchange,
originalRoutingKey,
messageBody,
msg -> {
// 移除死信标记(防止循环进入 DLQ)
msg.getMessageProperties().getHeaders().remove("x-death");
return msg;
}
);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
System.out.println("消息重新投递成功: " + messageBody);
} catch (Exception e) {
channel.basicNack(message.getMessageProperties().getDeliveryTag(),
false, true);
System.err.println("消息重新投递失败: " + e.getMessage());
}
}
// 从消息头中提取原始交换机名称
private String getOriginalExchange(Message message) {
List<Map<String,Object>> list = message.getMessageProperties()
.getHeader("x-death");
for (Map<String,Object> map : list){
return map.get("exchange").toString();
}
return message.getMessageProperties().getHeader("x-last-death-exchange").toString();
}
// 从消息头中提取原始路由键
private String getOriginalRoutingKey(Message message) {
List<Map<String,Object>> list = message.getMessageProperties()
.getHeader("x-death");
for (Map<String,Object> map : list){
List<String> routing_keys_list = (List<String>) map.get("routing-keys");
return routing_keys_list.get(0);
}
return "";
}
}
适用场景: 需自动处理大量死信消息。
实现方式:
1、消费者监听死信队列:编写消费者处理死信消息,按策略重试。
2、重试次数限制:通过 x-retry-count 头信息控制最大重试次数,避免无限循环。
死信队列
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* 死信队列消费 自动化重试机制
*/
@Component
public class DlxReceiver_2 {
private static final int MAX_RETRIES = 3; // 最大重试次数
@Autowired
private RabbitTemplate rabbitTemplate;
// 监听死信队列,执行重试逻辑
@RabbitListener(queues = "queue.dlx.a",ackMode = "MANUAL")
public void handleDlqMessage(Map<String,Object> messageBody,Channel channel, Message message) throws IOException {
try {
// 获取消息体和元数据
Map<String, Object> headers = message.getMessageProperties().getHeaders();
String originalExchange = getOriginalExchange(message);
String originalRoutingKey = getOriginalRoutingKey(message);
// 解析重试次数
int retryCount = (int) headers.getOrDefault("x-retry-count", 0);
if (retryCount >= MAX_RETRIES) {
// 超过最大重试次数,归档消息
archiveMessage(messageBody);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
System.out.println("消息已归档: " + messageBody);
return;
}
// 重试:更新重试次数并重新投递到原队列
headers.put("x-retry-count", retryCount + 1);
rabbitTemplate.convertAndSend(
originalExchange,
originalRoutingKey, // 直接发送到原队列(使用默认交换机)
messageBody,
msg -> {
msg.getMessageProperties().getHeaders().putAll(headers);
return msg;
}
);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
System.out.println("重试投递 (" + (retryCount + 1) + "/" + MAX_RETRIES + "): " + messageBody);
} catch (Exception e) {
channel.basicNack(message.getMessageProperties().getDeliveryTag(),
false, true);
System.err.println("重试处理失败: " + e.getMessage());
}
}
// 归档消息(模拟实现)
private void archiveMessage(Map<String,Object> message) {
// 实际可存储到数据库、文件或发送到归档队列
System.out.println("归档消息: " + message);
}
// 从消息头中提取原始交换机名称
private String getOriginalExchange(Message message) {
List<Map<String,Object>> list = message.getMessageProperties()
.getHeader("x-death");
for (Map<String,Object> map : list){
return map.get("exchange").toString();
}
return message.getMessageProperties().getHeader("x-last-death-exchange").toString();
}
// 从消息头中提取原始路由键
private String getOriginalRoutingKey(Message message) {
List<Map<String,Object>> list = message.getMessageProperties()
.getHeader("x-death");
for (Map<String,Object> map : list){
List<String> routing_keys_list = (List<String>) map.get("routing-keys");
return routing_keys_list.get(0);
}
return "";
}
}
正常消息消费:
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@Component
@RabbitListener(queues = "queue.normal.a",ackMode = "MANUAL")//监听的队列名称 TestDirectQueue
public class DirectReceiver_3 {
@RabbitHandler
public void process(Map<String,Object> testMessage,Channel channel, Message message) throws IOException {
try {
/**
* TODO 业务处理
*/
System.out.println("get msg2 success msg = "+testMessage);
channel.basicNack(message.getMessageProperties().getDeliveryTag(),
false, false);
} catch (Exception e) {
//消费者处理出了问题,需要告诉队列信息消费失败
/**
* 拒绝确认消息:
* channel.basicNack(long deliveryTag, boolean multiple, boolean requeue) ;
* deliveryTag:该消息的index
* multiple:是否批量.true:将一次性拒绝所有小于deliveryTag的消息。
* requeue:被拒绝的是否重新入队列
*/
channel.basicNack(message.getMessageProperties().getDeliveryTag(),
false, false);
/**
* 拒绝一条消息:
* channel.basicReject(long deliveryTag, boolean requeue);
* deliveryTag:该消息的index
* requeue:被拒绝的是否重新入队列
*/
//channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
/**
* TODO: 业务处理
*/
System.err.println("get msg2 failed msg = "+testMessage);
}
}
}
死信消息消费:
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* 死信队列消费 自动化重试机制
*/
@Component
public class DlxReceiver_2 {
private static final int MAX_RETRIES = 3; // 最大重试次数
@Autowired
private RabbitTemplate rabbitTemplate;
// 监听死信队列,执行重试逻辑
@RabbitListener(queues = "queue.dlx.a",ackMode = "MANUAL")
public void handleDlqMessage(Map<String,Object> messageBody,Channel channel, Message message) throws IOException {
try {
// 获取消息体和元数据
Map<String, Object> headers = message.getMessageProperties().getHeaders();
String originalExchange = getOriginalExchange(message);
String originalRoutingKey = getOriginalRoutingKey(message);
// 解析重试次数
int retryCount = (int) headers.getOrDefault("x-retry-count", 0);
if (retryCount >= MAX_RETRIES) {
// 超过最大重试次数,归档消息
archiveMessage(messageBody);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
System.out.println("消息已归档: " + messageBody);
return;
}
// 重试:更新重试次数并重新投递到原队列
headers.put("x-retry-count", retryCount + 1);
rabbitTemplate.convertAndSend(
originalExchange,
originalRoutingKey, // 直接发送到原队列(使用默认交换机)
messageBody,
msg -> {
msg.getMessageProperties().getHeaders().putAll(headers);
return msg;
}
);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
System.out.println("重试投递 (" + (retryCount + 1) + "/" + MAX_RETRIES + "): " + messageBody);
} catch (Exception e) {
channel.basicNack(message.getMessageProperties().getDeliveryTag(),
false, true);
System.err.println("重试处理失败: " + e.getMessage());
}
}
// 归档消息(模拟实现)
private void archiveMessage(Map<String,Object> message) {
// 实际可存储到数据库、文件或发送到归档队列
System.out.println("归档消息: " + message);
}
// 从消息头中提取原始交换机名称
private String getOriginalExchange(Message message) {
List<Map<String,Object>> list = message.getMessageProperties()
.getHeader("x-death");
for (Map<String,Object> map : list){
return map.get("exchange").toString();
}
return message.getMessageProperties().getHeader("x-last-death-exchange").toString();
}
// 从消息头中提取原始路由键
private String getOriginalRoutingKey(Message message) {
List<Map<String,Object>> list = message.getMessageProperties()
.getHeader("x-death");
for (Map<String,Object> map : list){
List<String> routing_keys_list = (List<String>) map.get("routing-keys");
return routing_keys_list.get(0);
}
return "";
}
}
适用场景: 消息涉及业务关键逻辑,需人工审核。
工具支持:
1、管理界面:通过 RabbitMQ 管理界面直接查看消息内容并导出。
2、日志系统:将死信消息记录到日志或数据库供人工分析。