什么是死信队列
DLX ,全称为 Dead-Letter-Exchange ,可以称之为死信交换器,也有人称之为死信邮箱。当消息在一个队列中变成死信( dead message )之后,它能被重新被发送到另一个交换器中,这个交换器就是 DLX ,绑定 DLX 的队列就称之为死信队列。
消息变成死信一般是由于以下几种情况:
- 〈1〉消息被拒绝( Basic.Reject/Basic.Nack ),井且设置 requeue 参数为 false;
- 〈2〉消息过期;
- 〈3〉队列达到最大长度。
DLX 也是一个正常的交换器,和一般的交换器没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性。当这个队列中存在死信时RabbitMQ 就会自动地将这个消息新发布到设置的 DLX 上去,进而被路由到另一个队列,即死信队列。可以监听这个队列中的消息、以进行相应的处理。
配置死信队列
在 channel.queueDeclare 方法中设置 x-dead-letter-exchange 参数来为这个队列添加 DLX
一、消息过期
1、添加配置
mq:
queueBinding:
queue: prod_queue_pay
dlQueue: dl-queue
exchange:
name: exchang_prod_pay
dlTopicExchange: dl-topic-exchange
key: prod_pay
dlRoutingKey: dl-routing-key
2、创建死信交换机、死信队列以及两者的绑定
@Value("${mq.queueBinding.exchange.dlTopicExchange}")
private String dlTopicExchange;
@Value("${mq.queueBinding.dlRoutingKey}")
private String dlRoutingKey;
@Value("${mq.queueBinding.dlQueue}")
private String dlQueue;
//创建死信交换机
@Bean
public TopicExchange dlTopicExchange(){
return new TopicExchange(dlTopicExchange,true,false);
}
//创建死信队列
@Bean
public Queue dlQueue(){
return new Queue(dlQueue,true);
}
//死信队列与死信交换机进行绑定
@Bean
public Binding BindingErrorQueueAndExchange(Queue dlQueue, TopicExchange dlTopicExchange){
return BindingBuilder.bind(dlQueue).to(dlTopicExchange).with(dlRoutingKey);
}
3、创建业务队列、业务交换机,以及两者的绑定
@Value("${mq.queueBinding.queue}")
private String queueName;
@Value("${mq.queueBinding.exchange.name}")
private String exchangeMame;
@Value("${mq.queueBinding.key}")
private String key;
private final String dle = "x-dead-letter-exchange";
private final String dlk = "x-dead-letter-routing-key";
/**
* 业务队列
* @return
*/
@Bean
public Queue payQueue(){
Map params = new HashMap<>();
//设置队列的过期时间
params.put(ttl,10000);
//声明当前队列绑定的死信交换机
params.put(dle,dlTopicExchange);
//声明当前队列的死信路由键 如果没有指定,则使用原队列的路由键:
params.put(dlk,dlRoutingKey);
return QueueBuilder.durable(queueName).withArguments(params).build();
}
@Bean
public TopicExchange payTopicExchange(){
return new TopicExchange(exchangeMame,true,false);
}
//队列与交换机进行绑定
@Bean
public Binding BindingPayQueueAndPayTopicExchange(Queue payQueue, TopicExchange payTopicExchange){
return BindingBuilder.bind(payQueue).to(payTopicExchange).with(key);
}
生产者代码
/*
* 生产者
*/
@Component
@Slf4j
public class RabbitSender {
@Value("${mq.queueBinding.exchange.name}")
private String exchangeName;
@Value("${mq.queueBinding.key}")
private String key;
@Autowired
private RabbitTemplate rabbitTemplate;
public void send(String msg){
log.info("RabbitSender.send() msg = {}",msg);
rabbitTemplate.convertAndSend(exchangeName,key,msg);
}
}
提供对外方法
@Autowired
private RabbitSender rabbitSender;
@GetMapping
public void test(@RequestParam String msg){
rabbitSender.send(msg);
}
启动服务,可以看到同时创建了业务队列、业务交换机以及死信队列、死信交换机
image.png
image.png
然后调用接口:[http://localhost:8080/?msg=红红火火]
image.png
在这 10s 内没有消费者消费这条消息,那么判定这条消息为过期消息。由于设置了 DLX ,过期之时消息被丢给dlxExchange 交换器中,这时找到与 dlxExchange匹配的队列 dlQueue后,消息被存储在 dlxQueue 这个死信队列中。
二、消息被拒绝
将业务队列的过期时间去掉,重新生成业务队列
/**
* 业务队列
* @return
*/
@Bean
public Queue payQueue(){
Map params = new HashMap<>();
//声明当前队列绑定的死信交换机
params.put(dle,dlTopicExchange);
//声明当前队列的死信路由键 如果没有指定,则使用原队列的路由键:
params.put(dlk,dlRoutingKey);
return QueueBuilder.durable(queueName).withArguments(params).build();
}
配置文件新增配置
rabbitmq:
listener:
simple:
#消息确认方式 manual手动确认 auto自动确认 none不管
acknowledge-mode: manual
添加消费者
/**
* 消费者
*/
@Component
@Slf4j
public class RabbitReceiver {
//测试消费者进行消费发送异常 是否进入死信队列
@RabbitListener(queues = "${mq.queueBinding.queue}")
public void infoConsumption(String data, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception {
log.info("收到信息:{}",data);
boolean ack = false;
Exception exception = null;
try {
if(data.contains("888")){
throw new RuntimeException("信息敏感");
}
} catch (RuntimeException e) {
ack = true;
exception = e;
}
if (ack){
log.error("消息消费发生异常,error msg:{}", exception.getMessage(), exception);
//注意第三个参数需要为false
//true则重新入队列,否则丢弃或者进入死信队列。
channel.basicNack(tag, false, false);
} else {
channel.basicAck(tag, false);
}
}
}
在上述代码中,如果将第三个参数设置为true
,将会出现无限重试问题。
在下一篇中笔者将介绍怎么解决无限重试问题:https://www.jianshu.com/p/5e0455152978