理论和实践详解RabbitMQ死信(dead lettering)(带测试样例和分析)

1 缘起

接上两篇:
RabbitMQ优先级队列
RabbitMQ惰性/延迟队列
继续补充RabbitMQ队列知识,
啥是死信?
即无法被某个原生队列消费的消息
(可能由于消息过期、拒绝消费和达到队列存储上限产生),
但是,消息也不应该不处理直接丢弃,
因此,统一将该类信息转储到死信队列,
当然,不是天然就可以转储的,需要为原生队列配置死信相关信息,如死信交换器、死信路由键,
最重要,是保证存储死信的队列正常工作。

2 死信架构

死信架构如下图所示,
由图可知,默认队列default-queue正常入队和出队消息,
当入队和出队发生消息成为死信的状况时,
会将该死信消息发送到为队列default-queue配置的存储死信消息的队列:dlx-queue,
死信队列、路由键和队列是独立于正常队列的一套完整入队出队系统,
只不过该系统专门用于处理死信(消息)。

在这里插入图片描述

3 死信交换器

队列的消息可以为“死信”,正常情况下,“死信”会从正常的队列中移除,进入专门的“死信”队列,
以下情况会将消息重发到死信交换器:

  • 消费者使用basic.reject或者basic.nack配置requeue参数为false;
  • 队列中消息过期(TTL);
  • 消息数达到队列长度上限被丢弃。

注意:过期的队列不会使其存储的消息成为死信。
死信交换器(DLX)是普通的交换器,可以随意使用,对于任何队列,DLX可以通过队列参数配置,或者使用Policy设置(运行时)。

3.1 通过Policy配置(运行时)

队列可以通过Policy配置死信交换器属性,
Linux和Windows客户端命令如下:
为队列dead-letter-queue添加死信交换器,改队列的数据成为死信后,
会由交换器my-dlx-ex发送到指定队列。

  • Linux操作系统
rabbitmqctl set_policy DLX "^dead-letter-queue$" '{"dead-letter-exchange":"my-dlx-ex"}'  --vhost /tutorial --apply-to queues
  • Windows操作系统
rabbitmqctl set_policy DLX "^dead-letter-queue$" "{""dead-letter-exchange"":""my-dlx-ex""}" --vhost /tutorial --apply-to queues

3.2 通过参数配置(声明时)

通过参数亦可以在声明队列时指定死信交换器。
新建队列myqueue,声明死信some.exchange.name交换器。
声明队列时可以不必声明交换器,但是,当消息为死信时,交换器应该就绪(存在),
如果交换器不存在,消息会被悄悄滴删除。
配置代码段如下。

channel.exchangeDeclare("some.exchange.name", "direct");

Map<String, Object> args = new HashMap<String, Object>();
args.put("x-dead-letter-exchange", "some.exchange.name");
channel.queueDeclare("myqueue", false, false, false, args);

发送死信时可以指定路由键,如果路由键未设定,则使用消息自身的路由键。
指定死信队列并且声明队列配置权限时,用户需要有读写交换器的权限,

args.put("x-dead-letter-routing-key", "some-routing-key");

3.3 路由死信消息

死信消息会路由到死信交换器,如:

  • 队列指定了路由键
  • 使用与最初发布相同的路由键

比如使用路由键foo通过交换器向队列发送消息,消息为死信时,消息会通过路由键foo发送到死信交换器。如果队列消息最初指定的死信路由键x-dead-letter-routing-key为bar,死信消息会通过路由键bar发送到死信交换器。

3.4 安全性

默认情况下,死信消息是重新发布的,发布者不会进行发布确认,因此,RabbitMQ集群环境中使用DLX不能保证是安全的。当消息发送到DLX绑定的队列后,消息会会立即从原始队列移除。这样虽然可以确保频繁的消息创建不会耗尽Broker资源,但是,消息有可能丢失(目标队列无法接收消息时)。

3.5 死信对消息的影响

死信消息修改头:

  • 交换器名称被最新的死信交换器替换
  • 路由键被指定队列的死信替换

死信进程为每个死信消息添加一个x-death的数组,该数组包含每个死信事件的实体,通过{queue, reason}标识,每个实体包含的内容有:

序号 参数 描述
1 queue 消息成为死信前所在队列的队列名称
2 reason 消息成为死信的原因
3 time 消息成为死信的日期和时间
4 exchange 消息发送到的交换器(如果消息有多个死信时间,交换器为死信交换器)
5 routing-keys 消息发送的路由键
6 count 当前死信原因中,消息成为死信的次数
7 original-expiration 如果消息成为死信是由于消息过期,消息原始过期属性,移除过期属性防止在死信队列中再次过期

新版本中增加了新的x-death属性。reason属性细分了死信原因:

  • rejected:消息由requeue参数设置为false而拒绝
  • expired:消息过期
  • maxlen:消息数量达到队列上限
  • delivery_limit:消息返回次数超过上限

死信消息的头前三级为:x-first-death-reason、x-first-death-queue和x-first-death-exchange。

4 Code实践

实践部分,给出4种消息成为死信的样例。

  • 消息过期;
  • 队列消息达到设定上限;
  • basic.reject配置requeue为fasle;
  • basic.nack配置requeue为false;

4.1 消息过期进入死信

通过x-message-ttl配置队列消息过期时间,单位为:毫秒,
这里设置消息过期时间为20秒,
建立队列并填入数据,
测试样例如下:

package com.monkey.java_study.mq.rabbitmq;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * RabbitMQ DLX生产者:消息过期.
 *
 * @author xindaqi
 * @since 2022-07-29 12:11
 */
public class RabbitMQDlxTtlProvider {
   

    public static void main(String[] args) {
   
        try (Connection connection = RabbitMQConnection.connectionFactoryDefault(); Channel channel = connection.createChannel()) {
   
            // 通用配置
            boolean durableFlag = true;
            boolean exclusiveFlag = false;
            boolean autoDeleteFlag = false;

            // DLX配置
            String dlxQueue = "dlx-queue";
            String dlxExchange = "dlx-exchange";
            String dlxRoutingKey = "dlx-routing-key";
            channel.queueDeclare(dlxQueue, durableFlag, exclusiveFlag, autoDeleteFlag, null);
            channel.exchangeDeclare(dlxExchange, BuiltinExchangeType.DIRECT);
            channel.queueBind(dlxQueue, dlxExchange, dlxRoutingKey);

            // 普通Queue配置
            String defaultQueue = "default-queue";
            String defaultExchange = "default-exchange";
            String defaultRoutingKey = "default-routing-key";

            Map<String, Object> defaultArgs = new HashMap<>(4);
            // 消息20秒过期
            defaultArgs.put("x-message-ttl", 20000);
            // 指定死信交换器
            defaultArgs.put("x-dead-letter-exchange", dlxExchange);
            // 指定死信路由键
            defaultArgs.put("x-dead-letter-routing-key", dlxRoutingKey);
            channel.queueDeclare(defaultQueue, durableFlag, exclusiveFlag
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天然玩家

坚持才能做到极致

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值