DLX,Dead-Letter-Exchange(死信邮箱)
利用DLX,当消息在一个队列中变成死信后,它能被重新publish到另一个Exchange,这个Exchange就是DLX。消息变成死信一向有以下几种情况:
- 消息被拒绝(basic.reject or basic.nack)并且requeue=false
- 消息TTL过期
- 队列达到最大长度
DLX也是一下正常的Exchange同一般的Exchange没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性,当这个队列中有死信时,RabbitMQ就会自动的将这个消息重新发布到设置的Exchange中去,进而被路由到另一个队列,publish可以监听这个队列中消息做相应的处理,这个特性可以弥补RabbitMQ 3.0.0以前支持的immediate参数中的向publish确认的功能。
JAVA的SDK设置
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);
你也可以为这个DLX指定routing key,如果没有特殊指定,则使用原队列的routing key
args.put("x-dead-letter-routing-key", "some-routing-key");
还可以使用policy来配置:
rabbitmqctl set_policy DLX ".*" '{"dead-letter-exchange":"my-dlx"}' --apply-to queues
“死信”被republish时,会在消息的header中增加一个叫做“x-death"的数组内容,包含了以下字段内容:
- queue - the name of the queue the message was in before it was dead-lettered,
- reason - see below,
- time - the date and time the message was dead lettered as a 64-bit AMQP format timestamp,
- exchange - the exchange the message was published to (note that this will be a dead letter exchange if the message is dead lettered multiple times), and
- routing-keys - the routing keys (including CC keys but excluding BCC ones) the message was published with.
- original-expiration (if the message was dead-letterered due to per-message TTL) - the originalexpiration property of the message. The expiration property is removed from the message on dead-lettering in order to prevent it from expiring again in any queues it is routed to.
The reason is a name describing why the message was dead-lettered and is one of the following:
- rejected - the message was rejected with requeue=false,
- expired - the TTL of the message expired; or
- maxlen - the maximum allowed queue length was exceeded.
在使用immediate参数时,如果消费者拒收消息这个应答是没有办法通知到publish的,但是利用DLX,拒收的消息会变成“死信”,这个死信会被republish到DLX路由的队列中去,分析这个新的信息的header中的x-death内容,可以判断出消息变成死信的原因,这样publish端可以做更灵活的处理。
TTL和DLX结合使用的一种场景
假如我需要一个这样的应用场景,consumer可以无条件退出,退出后我希望它监听的队列也一并删除掉,但是如果队列如果有消息,要求消息能被转发到DLX队列。
如果用queue的AUTO_DELETE功能,则consumer退出后,不管队列中是否有消息,队列都会被删除,这个方法不行。
我们可以这样的变通实现:
1、将队列的"x-expires"参数为300S,即队列在没有消费者空闲300S后将被自动删除 )
2、将队列的"x-message-ttl"参数为30S,即消息只存活30S
3、在生产者publish消息时,设置 immediate属性,即要求一publish时一定有一个消费者存在
这样子生产者不停的将消息成功放进了队列,由于publish时判断了immediate属性,也就是说publish消息时,消费者是一定存在的,假如由于消费者处理的不及时导致消息有所堆积,在30S内还没有处理的话,这些消息将会被republish到DLX路由的队列中保存,即使在这时消费者异常退出了,也没关系,因为队列至少也将在300S后才自动删除,有足够长的时间让所有消费都republish至DLX队列
这个应用场景是我自己乱想的,纯粹是为了学习RabbitMQ而想的,实际应用中可以有其它代替方案来实现,就当学习了。