【转】RabbitMQ入门_11_DLX

本文深入解析RabbitMQ中死信队列的工作原理,包括消息如何成为死信,死信交换机的作用,以及死信消息的再分发机制。探讨了死信消息的绑定键规则和死信消息的特性变化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自首夜盲毒预言家

参考资料:https://www.rabbitmq.com/dlx.html
 

队列中的消息可能会成为死信消息(dead lettered)。让消息成为死信消息的事件有:

  • 消息被取消确认(nack 或 reject),且设置为不重入队列(requeue = false)
  • 消息TTL过期
  • 队列达到长度限制

死信消息会被死信交换机(Dead Letter Exchange, DLX)重新发布。

gordon.study.rabbitmq.dlx.TestDlx.java

<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">TestDlx</span> {
 
    <span style="color:#0000ff">private</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">final</span> String DLX_EXCHANGE_NAME = <span style="color:#a31515">"exchangeDLX"</span>;
 
    <span style="color:#0000ff">private</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">final</span> String DLX_QUEUE_NAME = <span style="color:#a31515">"queueDLX"</span>;
 
    <span style="color:#0000ff">private</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">final</span> String QUEUE_NAME = <span style="color:#a31515">"queue"</span>;
 
    <span style="color:#0000ff">public</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">main</span>(String[] argv) <span style="color:#0000ff">throws</span> Exception {
        ConnectionFactory factory = <span style="color:#0000ff">new</span> ConnectionFactory();
        factory.setHost(<span style="color:#a31515">"localhost"</span>);
        Connection connection = factory.newConnection();
        Channel senderChannel = connection.createChannel();
        Channel consumerChannel = connection.createChannel();
 
        Map<String, Object> args = <span style="color:#0000ff">new</span> HashMap<String, Object>();
        args.put(<span style="color:#a31515">"x-message-ttl"</span>, 3000); <span style="color:green">// 设置队列中消息存活时间为3秒</span>
        args.put(<span style="color:#a31515">"x-max-length"</span>, 5); <span style="color:green">// 设置队列最大消息数量为5</span>
        args.put(<span style="color:#a31515">"x-dead-letter-exchange"</span>, DLX_EXCHANGE_NAME); <span style="color:green">// 设置DLX</span>
        senderChannel.queueDeclare(QUEUE_NAME, <span style="color:#0000ff">false</span>, <span style="color:#0000ff">false</span>, <span style="color:#0000ff">true</span>, args);
 
        senderChannel.queueDeclare(DLX_QUEUE_NAME, <span style="color:#0000ff">false</span>, <span style="color:#0000ff">false</span>, <span style="color:#0000ff">true</span>, <span style="color:#0000ff">null</span>);
        senderChannel.exchangeDeclare(DLX_EXCHANGE_NAME, <span style="color:#a31515">"direct"</span>, <span style="color:#0000ff">false</span>, <span style="color:#0000ff">true</span>, <span style="color:#0000ff">null</span>);
        <span style="color:green">// 将死信队列绑定到死信交换机上,绑定键为 QUEUE_NAME。消息发送时使用的绑定键也会是 QUEUE_NAME</span>
        senderChannel.queueBind(DLX_QUEUE_NAME, DLX_EXCHANGE_NAME, QUEUE_NAME);
 
        <span style="color:green">// 发布6个消息</span>
        <span style="color:#0000ff">for</span> (<span style="color:#0000ff">int</span> i = 0; i < 6;) {
            String message = <span style="color:#a31515">"NO. "</span> + ++i;
            senderChannel.basicPublish(<span style="color:#a31515">""</span>, QUEUE_NAME, <span style="color:#0000ff">null</span>, message.getBytes(<span style="color:#a31515">"UTF-8"</span>));
        }
 
        <span style="color:green">// 监视死信队列</span>
        Consumer dlxConsumer = <span style="color:#0000ff">new</span> DefaultConsumer(consumerChannel) {
            <span style="color:#2b91af">@Override</span>
            <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">handleDelivery</span>(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, <span style="color:#0000ff">byte</span>[] body)
                    <span style="color:#0000ff">throws</span> IOException {
                String message = <span style="color:#0000ff">new</span> String(body, <span style="color:#a31515">"UTF-8"</span>);
                System.out.printf(<span style="color:#a31515">"consume: %s, envelop: %s, properties: %s\n"</span>, message, envelope, properties);
            }
        };
        consumerChannel.basicConsume(DLX_QUEUE_NAME, <span style="color:#0000ff">true</span>, dlxConsumer);
 
        Thread.sleep(100);
        GetResponse resp = consumerChannel.basicGet(QUEUE_NAME, <span style="color:#0000ff">false</span>);
        consumerChannel.basicReject(resp.getEnvelope().getDeliveryTag(), <span style="color:#0000ff">false</span>);
    }
}
</code></span>

代码第19行通过 x-dead-letter-exchange 参数定义了当前队列指定的死信交换机是 DLX_EXCHANGE_NAME,当前队列中所有的死信消息都将交由 DLX_EXCHANGE_NAME 再一次分发到新的队列。

代码第23行创建了死信交换机 DLX_EXCHANGE_NAME,可见,死信交换机就是普通的交换机

从以上两部分代码顺序可知,死信交换机可以在被队列引用后才创建。RabbitMQ 不会去验证死信交换机设置是否有效,当死信消息找不到指定的交换机时,死信消息会被RabbitMQ安静的丢弃,而不是抛出异常

既然死信交换机就是普通的交换机,那么它就需要根据消息的绑定键来分发消息。死信消息的绑定键遵守以下规则:当队列指定了死信路由键(x-dead-letter-routing-key)参数时,死信消息使用该参数指定的路由键作为自己的路由键;否则使用消息原来的路由键。所以,示例代码中,死信消息的路由键是代码第30行发布消息时指定的路由键 QUEUE_NAME。如果想修改死信消息路由键,可以在第19行下面增加

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

 

观察日志输出:

consume: NO. 1, envelop: Envelope(deliveryTag=1, redeliver=false, exchange=exchangeDLX, routingKey=queue), properties: #contentHeader<basic>(content-type=null, content-encoding=null, headers={x-death=[{queue=queue, time=Sat Jun 10 11:51:48 CST 2017, count=1, reason=maxlen, routing-keys=[queue], exchange=}]}, delivery-mode=null, priority=null, correlation-id=null, reply-to=null, expiration=null, message-id=null, timestamp=null, type=null, user-id=null, app-id=null, cluster-id=null)
consume: NO. 2, envelop: Envelope(deliveryTag=3, redeliver=false, exchange=exchangeDLX, routingKey=queue), properties: #contentHeader<basic>(content-type=null, content-encoding=null, headers={x-death=[{queue=queue, time=Sat Jun 10 11:51:48 CST 2017, count=1, reason=rejected, routing-keys=[queue], exchange=}]}, delivery-mode=null, priority=null, correlation-id=null, reply-to=null, expiration=null, message-id=null, timestamp=null, type=null, user-id=null, app-id=null, cluster-id=null)
consume: NO. 3, envelop: Envelope(deliveryTag=4, redeliver=false, exchange=exchangeDLX, routingKey=queue), properties: #contentHeader<basic>(content-type=null, content-encoding=null, headers={x-death=[{queue=queue, time=Sat Jun 10 11:51:51 CST 2017, count=1, reason=expired, routing-keys=[queue], exchange=}]}, delivery-mode=null, priority=null, correlation-id=null, reply-to=null, expiration=null, message-id=null, timestamp=null, type=null, user-id=null, app-id=null, cluster-id=null)
......

死信消息相比原来的消息发生了一些改变。除了上面提到的 exchange 变为死信交换机名称,routingKey 可能变为新的路由键(由 x-dead-letter-routing-key 参数决定),死信处理过程还在死信消息头中增加了 x-death 数组信息。每一次死信事件对应一个数组项,包含以下字段,

  • queue:本次死信事件发生前,消息所属队列
  • reason:死信原因,分为 rejected、expired 与 maxlen
  • time:死信事件发生时间
  • exchange:死信事件前,消息发布时指定的交换机
  • routing-keys:死信事件前,消息发布时指定的路由键
  • count:当前 queue 与 当前 reason 表述的死信事件发生的次数
  • original-expiration:对于消息 TTL,因为超时导致死信时,会移除 TTL(否则永远触发超时),该字段记录原来设定的消息 TTL 值

当死信消息再次触发死信事件时,一般会产生一个新的数组项,插到数组的最前头。但是,如果 x-death 数组已经包含一个相同 queue 与 reason 的数组项,则直接将该数组项移到数组最前头,并将其 count 值加一。
 

未确认问题:
1. 当消息被 reject 回队列头,同时又超过队列长度限制时,怎么处理?
试验结果好像是直接变为 maxlen reason 的死信消息

2. DLX很可能形成环(最简单的场景就是DLX与原交换机相同),这时消息有可能无限触发死信事件吗(例如超过队列长度限制)?
官方说法是处在DLX环中的消息,如果经历了整个环都没有触发过 rejected reason 的死信事件,则抛弃该消息。

这些问题有点偏,目前就不花时间研究了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值