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

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

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

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

gordon.study.rabbitmq.dlx.TestDlx.java

public class TestDlx {

    private static final String DLX_EXCHANGE_NAME = "exchangeDLX";

    private static final String DLX_QUEUE_NAME = "queueDLX";

    private static final String QUEUE_NAME = "queue";

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

代码第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 的死信事件,则抛弃该消息。

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

RabbitMQ入门_11_DLX的更多相关文章

  1. 2.RABBITMQ 入门 - WINDOWS - 生产和消费消息 一个完整案例

    关于安装和配置,见上一篇 1.RABBITMQ 入门 - WINDOWS - 获取,安装,配置 公司有需求,要求使用winform开发这个东西(消息中间件),另外还要求开发一个日志中间件,但是也是要求 ...

  2. RabbitMQ入门-从HelloWorld开始

    从读者的反馈谈RabbitMQ 昨天发完<RabbitMQ入门-初识RabbitMQ>,我陆陆续续收到一些反馈.鉴于部分读者希望结合实例来讲 期待下篇详细,最好结合案例.谢谢! 哪都好,唯 ...

  3. RabbitMQ入门-高效的Work模式

    扛不住的Hello World模式 上篇<RabbitMQ入门-从HelloWorld开始>介绍了RabbitMQ中最基本的Hello World模型.正如其名,Hello World模型 ...

  4. RabbitMQ入门-消息订阅模式

    消息派发 上篇<RabbitMQ入门-消息派发那些事儿>发布之后,收了不少反馈,其中问的最多的还是有关消息确认以及超时等场景的处理. 楼主,有遇到消费者后台进程不在,但consumer连接 ...

  5. RabbitMQ入门-Topic模式

    上篇<RabbitMQ入门-Routing直连模式>我们介绍了可以定向发送消息,并可以根据自定义规则派发消息.看起来,这个Routing模式已经算灵活的了,但是,这还不够,我们还有更加多样 ...

  6. RabbitMQ入门与使用篇

    介绍 RabbitMQ是一个由erlang开发的基于AMQP(Advanced Message Queue)协议的开源实现.用于在分布式系统中存储转发消息,在易用性.扩展性.高可用性等方面都非常的优秀 ...

  7. [转]RabbitMQ入门教程(概念,应用场景,安装,使用)

    原文地址:https://www.jianshu.com/p/dae5bbed39b1 RabbitMQ 简介 RabbitMQ是一个在AMQP(Advanced Message Queuing Pr ...

  8. RabbitMQ 入门指南——安装

    RabbitMQ好文 Rabbitmq Java Client Api详解 tohxyblog-博客园-rabbitMQ教程系列 robertohuang-CSDN-rabbitMQ教程系列 Rabb ...

  9. RabbitMQ入门:总结

    随着上一篇博文的发布,RabbitMQ的基础内容我也学习完了,RabbitMQ入门系列的博客跟着收官了,以后有机会的话再写一些在实战中的应用分享,多谢大家一直以来的支持和认可. RabbitMQ入门系 ...

随机推荐

  1. js值类型和引用类型的区别

    1:赋值 值类型的赋值:直接将存储的数据赋值一份进行赋值,两份数据在内存中是完全独立的. 引用类型赋值:引用类型的赋值的时候,是将变量中的存储的地址赋值一份单独存储,但是两个变量中修改其中一个对象,另 ...

  2. linux phpize

    phpize是什么 1.phpize是用来扩展php扩展模块的,通过phpize可以建立php的外挂模块. 当php编译完后,在bin下面会有phpize这个脚本文件, 在编译你要添加的扩展模块之前, ...

  3. Integer类之成员变量

    一.一共11个成员变量. 二.详情介绍. 1.value值.这个是Integer类的唯一标志.最重要的实例属性. 2.最小值和最大值常量.注意,计算机里面是以补码形式保存的,因此用十六进制时,给的数据 ...

  4. bzoj2733: [HNOI2012]永无乡 启发式合并

    地址:http://www.lydsy.com/JudgeOnline/problem.php?id=2733 题目: 2733: [HNOI2012]永无乡 Time Limit: 10 Sec   ...

  5. Hive 复制分区表和数据

    1. 非分区表: 复制表结构: create table new_table as select * from exists_table where 1=0; 复制表结构和数据: create tab ...

  6. python在交互模式下直接输入对象后回车,调用的是对象的__repr__()方法,这个方法表示的是一个编码,用print+对象是调用对象的__str__方法

    交互模式下调用对象的__repr__()方法,这个方法表示的是一个编码 >>> u"国庆节快乐"u'\u56fd\u5e86\u8282\u5feb\u4e50' ...

  7. python多进程打印字符,加锁(Lock加锁)

    先看不加锁的: #coding=utf-8from multiprocessing import Process,Lockimport time def l(num):    #lock.acquir ...

  8. Python Web学习笔记之IGMP和ICMP的差别

    理论技术:TCP/IP协议族(四)ICMP和IGMP协议! 应该先说IP协议的,后来考虑到层次性,还是先把支撑协议介绍完在细说IP!因为IP是我的最爱也是我的痛!呵呵! 一.ICMP协议 为什么要使用 ...

  9. phonegap 开发案例

    PhoneGap-Android-HTML5-WebSocket 不使用任何框架,教你制作网页滑动切换效果 http://www.csdn.net/article/2012-04-17/2804644 ...

  10. lambda表达式Bug——修改捕获变量失败

    解<C++ Primer 5th>的 9-50 练习题时,遇到了 lambda表达式值捕获和引用捕获之区别问题. 欲修改捕获的变量 sum,累加之.但当时忘记值捕获和引用捕获是有区别的.下 ...