关于死信队列
死信队列简称DLX ,全称为 Dead-Letter-Exchange ,可以称之为死信交换器,也有人称之为死信邮箱。当 消息在一个队列中变成死信 (dea message) 之后,它能被重新被发送到另一个交换器中,这个交换器就是 DLX ,绑定 DLX 的队列就称之为死信队列。
“消息变成死信 般是由于以下几种情况:
channel.basicNack
或 channel.basicReject
,并且此时requeue
属性被设置为false
DLX 是一个正常的交换器,和一般的交换器没有区别,它能在任何的队列上被指定际上就是设置某个队列的属性。当这个队列中存在死信时 RabbitMQ 就会自动地将这个消息 新发布到设置的 DLX ,进而被路由到另一个队列,即死信队列。可以监听这个队列中的消息以进行相应的处理,这个特性与将消息的 TTL 设置为 配合使用可以弥补 imrnediate 参数(此参数将再另一篇文章中详细讲解) 的功能。 通过在 channel.queueDeclare 方法中设置 x-dead-letter-exchange 参数来为这 个队列添加 DLX
如何实现死信队列
首先我们需要创建两个交换机:业务交换机exchange.normal 和死信交换机exchange.dlx ,并分别绑定两个队列 :业务队列queue.normal和死信队列queue.dlx。然后通过在业务队列的声明中添加死信队列并指定死信交换机。
接下来看下基于Java如何具体实现一个由于消息过期,导致消息投递到死信队列的案例:
package com.htc.rabbit.producer.dlx;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.MessageProperties;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.Connection;
import java.util.HashMap;
import java.util.Map;
/**
* @author: hutingcong
* @date: 2020/5/16.
* @descr: 死信队列的简单实现
*/
public class DLX {
private final static String DLX_EXCHANGE = "exchange.dlx";
private final static String NORMAL_EXCHANGE = "exchange.normal";
private final static String DLX_ROUTING_KEY = "dlx.routing.key";
private final static String NORMAL_QUEUE = "queue.normal";
private final static String DLX_QUEUE = "queue.dlx";
private static CachingConnectionFactory factory;
static {
new DLX();
}
public DLX() {
factory = new CachingConnectionFactory("127.0.0.1");
factory.setUsername("guest");
factory.setPassword("guest");
factory.setPort(5672);
}
public static CachingConnectionFactory getInstance() {
return factory;
}
public static void dlx() throws Exception {
//创建一个信道,一个信道对应一个线程,
Connection connection = factory.createConnection();
Channel channel = connection.createChannel(false);
//创建业务交换机
channel.exchangeDeclare(NORMAL_EXCHANGE, "fanout", true);
//创建死信交换机
channel.exchangeDeclare(DLX_EXCHANGE, "direct", true);
Map args = new HashMap<>();
//设置队列的ttl, 时间单位为毫秒
args.put("x-message-ttl", 10000);
//指定死信交换机
args.put("x-dead-letter-exchange", DLX_EXCHANGE);
//指定死信路由键
args.put("x-dead-letter-routing-key", DLX_ROUTING_KEY);
//创建一个配置了死信交换机的业务队列
channel.queueDeclare(NORMAL_QUEUE, true, false, false, args);
//将业务队列绑定到业务交换机,因为业务交换机类型为fanout,所以无需指定路由键
channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "");
//创建死信队列
channel.queueDeclare(DLX_QUEUE, true, false, false, null);
//将死信队列绑定到死信交换机并指定路由键,死信交换机类型为direct,(注意这里的路由键要和业务队列中arguments参数中配置的路由键一致,不然会导致死信交换机找不到匹配的死信队列,然后丢失消息)
channel.queueBind(DLX_QUEUE, DLX_EXCHANGE, DLX_ROUTING_KEY);
channel.basicPublish(NORMAL_EXCHANGE, "aaaaa", MessageProperties.PERSISTENT_TEXT_PLAIN, "this is a message about dlx !".getBytes());
channel.close();
connection.close();
}
public static void main(String[] args) {
try {
dlx();
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意:由于这里选择的死信交换机是direct类型的,在将死信交换机和死信队列通过路由键绑定的时候,这个路由键必须和在声明业务队列时args参数里配置的路由键一致,即:args.put("x-dead-letter-routing-key", DLX_ROUTING_KEY),不然会导致死信交换机找不到匹配的死信队列,然后丢失消息
代码运行后可观察web管理页面。在消息未过期前,刚被投入的消息存放在业务队列queue.normal里面:
待消息过期后(这里代码里设置的过期时间是10s),会被自动投递到死信队列queue.dlx中:
大致的流程是:生产者首先发送一条携带路由键为 aaaaa 的消息(由于业务交换机的类型是fanout,所以这里的路由键类型可以任意字符串),然后经过交换器 exchange.normal 顺利地存储到队列 queue.normal 。由于队 queue.normal 设置了过期时间为 10s 在这 10s 内没有消费者消费这条消息,那么判定这条消息为过期。由于队列queue.normal设置了 DLX 过期时时间,消息过期后被丢给交换器 exchange.dlx 中,这时 exchange.dlx 将消息传递给匹配的队列 queue dlx,随后将消息存储在 queue.dlx 这个死信队列中。具体的流程图如下图:
DLX可以处理异常情况下,消息不能够被消费者正确消费(消费者调用了 Basic.Nack 或者 Basic.Reject) 而被置入死信队列中的情况,后续分析程序可以通过消费这个死信队列中的内容来分析当时所遇到的异常情况,进而可以改善和优化系统。
本文参考文献:【Rabbitmq实战指南】