【RabbitMQ 笔记】— 死信队列
死信交换器,Dead Letter Exchange,下文简称 DLX。当消息在一个队列中变成死信(Dead Letter)之后,它会被发送到另一个交换器中,这个交换器就是 DLX,绑定 DLX 的队列就称为死信队列。
消息变成死信一般由以下几种情况:
- 消息被拒绝(Basic.Reject / Basic.Nack),并且设置 requeue 参数为 false
- 消息过期
- 队列长度达到最大值
那就先聊聊过期时间
过期时间(TTL)
TTL,全称 time to live。RabbitMQ 里分为消息的过期时间和队列的过期时间
消息的 TTL
有两种方式可以设置消息的过期时间
- 通过队列的属性设置,这样的话队列中所有的消息过期时间一致。设置方法是在 channel.queueDeclare 方法中加入 x-message-ttl 参数实现,单位毫秒。如果不设置 TTL,则表示消息不会过期;如果设置为 0, 除非此时消息可以立即投递给消费者,否则消息会被丢失。
- 每个消息单独设置,设置方法为在 channel.basicPublish 方法中加入 expiration 属性参数,单位毫秒。
- 如果上面两种都设置了,那么以小的为准。消息在队列中生存时间超过了 TTL 值时,就会变成死信(Dead Letter)。
其实上面两种设置过期时间的方式判定消息是否过期的逻辑也是不一样的,区别如下
- 通过队列的方式,队列中消息的过期时间都是一样的,已过期的消息肯定在队列的头部,那么 RabbitMQ 只用定期从队头开始扫描看是否有消息过期即可
- 消息单独设置,每个消息的过期时间不同,如果要删除所有过期消息势必要扫描整个队列,所以不如等到消息消费时再判定是否过期,如果过期删除即可。
队列的 TTL
可以通过 channel.queueDeclare 方法中 x-expires 参数(单位毫秒,不能设置为 0)可以可控制队列被自动删除前未使用状态的时间。未使用定义如下:
- 队列上没有任何消费者
- 队列也没有被重新声明
- 过期时间段内没有调用过 Basic.Get 命令
死信队列
DLX 也是个普通的交换器,和一般的交换器没有区别。当一个队列存在死信时,RabbitMQ 会把消息发送给 DLX,进而被路由到另一个队列,这个队列就是死信队列。然后可以监听这个队列中的消息进行相应的处理。这个特性与将消息的 TTL 设置为 0 配合使用可以弥补 immediate 参数的功能。
通过 channel.queueDeclare 方法中设置 x-dead-letter-exchange 参数来为队列添加 DLX。也可以为这个 DLX 指定路由键,设置 x-dead-letter-routing-key 参数指定。如果没有指定,则使用原队列的路由键。
具体代码如下
try(Connection connection = ConnectionUtil.getConnection()) {
Channel channel = connection.createChannel();
// 创建死信交换器, 死信队列
channel.exchangeDeclare(DLX, BuiltinExchangeType.FANOUT.getType(), true, false, null);
channel.queueDeclare(DLX_QUEUE, true, false, false, null);
channel.queueBind(DLX_QUEUE, DLX, "");
// 创建 normalExchange
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT.getType(), true, false, null);
Map<String, Object> args = new HashMap<>(2);
args.put("x-message-ttl", EXPIRE_TIME); // 设置队列消息的过期时间
args.put("x-dead-letter-exchange", DLX); // 为队列添加死信交换器
channel.queueDeclare(NORMAL_QUEUE, true, false, false, args);
channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, ROUTING_KEY);
// 发送消息
byte[] bytes = "dlx message coming!".getBytes();
channel.basicPublish(NORMAL_EXCHANGE, ROUTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN, bytes);
} catch (IOException e) {
e.printStackTrace();
}
上述代码示意图如下
对于 RabbitMQ 来说,DLX 是个很有用的特性,他可以处理异常情况下,消息不能够正常消费(消费者调用了 Basic.Nac 或者 Basic.reject)而被置入死信队列的情况,后续分析程序可以通过消费这个死信队列中的内容来分析当时遇到的异常情况,进而可以进一步优化。另外,DLX 配合 TTL 还可以实现延时队列的功能。