1、备份交换器(AE)
备份交换器(Alternate Exchange,简称AE)。
生产者在发送消息的时候如果不设置mandatory参数,那么消息在未被路由的情况下将会丢失;如果设置了mandatory参数,那么需要添加ReturnListener的编程逻辑,生产者的代码将变得复杂。
如果既不想复杂化生产者的编程逻辑,又不想消息丢失,那么可以使用备份交换器,这样可以将未被路由的消息存储在RabbitMQ中,再在需要的时候去处理这些消息。
可以通过在声明交换器(调用channel.exchangeDeclare
方法)的时候添加alternate-exchange
参数来实现,也可以通过策略(Policy,详细参考6.3节)的方式实现。如果两者同时使用,则前者的优先级更高,会覆盖掉Policy的设置。
Map<String, Object> args = new HashMap<String, Object>();
args.put("alternate-exchange", "myAe"); // normalExchange参数中设置备份交换器myAe
channel.exchangeDeclare("normalExchange", "direct", true, false, args);
channel.exchangeDeclare("myAe", "fanout", true, false, null); // 声明备份交换器myAe
channel.queueDeclare("normalQueue", true, false, false, null);
channel.queueBind("normalQueue", "normalExchange", "normalKey");
channel.queueDeclare("unroutedQueue", true, false, false, null);
channel.queueBind("unroutedQueue", "myAe", "");
备份交换器其实和普通的交换器没有太大的区别,为了方便使用,建议设置为fanout类型。
对于备份交换器,总结了以下几种特殊情况:
- 如果设置的备份交换器不存在,客户端和RabbitMQ服务端都不会有异常出现,此时消息会丢失。
- 如果备份交换器没有绑定任何队列,客户端和RabbitMQ服务端都不会有异常出现,此时消息会丢失。
- 如果备份交换器没有任何匹配的队列,客户端和RabbitMQ服务端都不会有异常出现,此时消息会丢失。
- 如果备份交换器和mandatory参数一起使用,那么mandatory参数无效。
2、过期时间(TTL)
TTL, Time to Live 的简称,即过期时间 。 RabbitMQ 可以对消息和队列设置 TTL。
2.1 设置消息的 TTL
消息在队列中的生存时间一旦超过设置 的 TTL 值时,就会变成"死信" (Dead Message) ,消费者将无法再收到该消息。目前有两种方法可以设置消息的 TTL。
-
第一种方法是通过队列属性设置,队列中所有消息都有相同的过期时间。
通过队列属性设置消息TTL的方法是在channel.queueDeclare
方法中加入x-message-ttl
参数实现的,这个参数的单位是毫秒。Map<String, Object> argss = new HashMap<String, Object>(); argss.put("x-message-ttl",6000); channel.queueDeclare(queueName, durable, exclusive, autoDelete, argss);
-
第二种方法是对消息本身进行单独设置,每条消息的TTL可以不同。如果两种方法一起使用,则消息的TTL取两者之间较小的那个数值为准。
针对每条消息设置TTL的方法是在channel.basicPublish
方法中加入expiration
的属性参数,单位为毫秒。AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder(); builder.deliveryMode(2);//持久化消息 builder.expiration("60000");//设置TTL=60000ms AMQP.BasicProperties properties = builder.build(); channel.basicPublish(exchangeName, routingKey, mandatory, properties, "ttlTestMessage".getBytes());
对于第一种设置队列 TTL 属性的方法,一旦消息过期,就会从队列中抹去,而在第二种方法中,即使消息过期,也不会马上从队列中抹去,因为每条消息是否过期是在即将投递到消费者之前判定的。
为什么这两种方法处理的方式不一样?
因为第一种方法里,队列中己过期的消息肯定在队列头部, RabbitMQ 只要定期从队头开始扫描是否有过期的消息即可。而第二种方法里,每条消息的过期时间不同,如果要删除所有过期消息势必要扫描整个队列,所以不如等到此消息即将被消费时再判定是否过期 , 如果过期再进行删除即可。
2.2 设置队列的TTL
通过channel.queueDeclare
方法中的x-expires
参数可以控制队列被自动删除前处于未使用状态的时间。未使用的意思是队列上没有任何的消费者,队列也没有被重新声明,并且在过期时间段内也未调用过Basic.Get
命令。
RabbitMQ 会确保在过期时间到达后将队列删除,但是不保障删除的动作有多及时 。在 RabbitMQ 重启后,持久化的队列的过期时间会被重新计算。
用于表示过期时间的x-expires
参数以毫秒为单位,并且服从和x-message-ttl
一样的约束条件,不过不能设置为0
。
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-expires", 1800000);
channel.queueDeclare("myqueue", false, false, false, args);
3、死信队列
DLX (Dead-Letter-Exchange) 死信交换器。当消息在一个队列中变成死信 (dead message) 之后,它能被重新被发送到另一个交换器中,这个交换器就是 DLX ,绑定 DLX 的队列就称之为死信队列。
消息变成死信一般是由于以下几种情况:
- 消息被拒绝 (Basic.Reject/Basic.Nack) ,井且设置 requeue 参数为 false;
- 消息TTL过期;
- 队列达到最大长度。
DLX 也是一个正常的交换器,和一般的交换器没有区别,它能在任何的队列上被指定,实 际上就是设置某个队列的属性。当这个队列中存在死信时,RabbitMQ 就会自动地将这个消息重新发布到设置的DLX 上去,进而被路由到另一个队列,即死信队列。
通过在channel.queueDeclare
方法中设置x-dead-letter-exchange
参数来为这个队列添加DLX。
channel.exchangeDeclare("exchange.dlx", "direct", true);
channel.exchangeDeclare("exchange.normal", "fanout", true);
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-message-ttl", 10000);
// 为队列设置死信交换器DLX
args.put("x-dead-letter-exchange", "exchange.dlx");
// 可以为DLX指定路由键,如果没有特殊指定,则使用原队列的路由键
args.put("x-dead-letter-routing-key", "routingkey");
channel.queueDeclare("queue.normal", true, false, false, args);
channel.queueBind("queue.normal", "exchange.normal", "");
channel.queueDeclare("queue.dlx", true, false, false, null);
channel.queueBind("queue.dlx", "exchange.dlx", "routingkey");
channel.basicPublish("exchange.normal", "rk", MessageProperties.PERSISTENT_TEXT_PLAIN, "dlx".getBytes());
对于 RabbitMQ 来说, DLX 是一个非常有用的特性。它可以处理异常情况下,消息不能够被消费者正确消费(消费者调用了 Basic.Nack 或者 Basic.Reject) 而被置入死信队列中的情况,后续分析程序可以通过消费这个死信队列中的内容来分析当时所遇到的异常情况,进 而可以改善和优化系统。
死信的处理方式:
- 丢弃,如果不是很重要,可以选择丢弃
- 记录死信入库,然后做后续的业务分析或处理
- 通过死信队列,由负责监听死信的应用程序进行处理
4、延迟队列
延迟队列存储的对象是对应的延迟消息,所谓"延迟消息"是指当消息被发送以后,并不想让消费者立刻拿到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费。
延迟队列的使用场景有很多,比如:
- 在订单系统中,一个用户下单之后通常有 30 分钟的时间进行支付,如果30分钟之内没有支付成功,那么这个订单将进行异常处理,这时就可以使用延迟队列来处理这些订单了。
- 用户希望通过手机远程遥控家里的智能设备在指定的时间进行工作。这时候就可以将 用户指令发送到延迟队列,当指令设定的时间到了再将指令推送到智能设备 。
在 AMQP 协议中,或者 RabbitMQ 本身没有直接支持延迟队列的功能,但是可以通过DLX 和TTL模拟出延迟队列的功能。
5、优先级队列
优先级队列,顾名思义,具有高优先级的队列具有高的优先权,高优先级的消息具备优先被消费的特权。
可以通过设置队列的x-max-priority参数来实现:
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-max-priority", 10);
channel.queueDeclare("queue.priority", true, false, false, args);
上面的代码演示的是如何配置一个队列的最大优先级。在此之后,需要在发送时在消息中设置消息当前的优先级:
AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
builder.priority(5);
AMQP.BasicProperties properties = builder.build();
channel.basicPublish("exchange_priority","rk_priority",properties,("messages").getBytes());
上面的代码中设置消息的优先级为5。默认最低为0,最高为队列设置的最大优先级。
优先级高的消息可以被优先消费,这个也是有前提的:如果在消费者的消费速度大于生产者的速度且Broker中没有消息堆积的情况下,对发送的消息设置优先级也就没有什么实际意义。