在正常情况下,生产者产生并发送一条消息然后被交换器正确路由到某个队列中。但是如果一条消息不能被正确路由到某个队列时,那么这条消息该何去何从呢?RabbitMQ提供以下几个处理方案:
(1)将消息返回给生产者;
(2)直接将消息丢失;
(3)使用备份交换器将未能被路由的消息存储起来。
1. 返回给生产者
void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body)
throws IOException;
在上面这个方法,mandatory
和immediate
两个参数都有当消息不能被正确路由时将消息返回给生产者的功能。
- mandatory
当mandatory
参数设置为true时,交换器无法根据自身的类型和路由键找到一个符合条件的队列,那么RabbitMQ会调用Basic.Return
命令将消息返回给生产者。当该参数设置为false时,出现上述情形,则消息直接被丢弃。
生产者可通过调用channel.addReturnListener
来添加返回的消息。
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消息不能被正确路由,消息体:" + new String(body));
}
});
// 设置mandatory参数为true
channel.basicPublish("demo.direct.exchange", "demo.routingKey not exists", true, MessageProperties.PERSISTENT_TEXT_PLAIN, "mandatory is true, but routingKey not exists.".getBytes());
- immediate
当immediate
参数设置为true时,如果交换器在将消息路由到队列时发现队列上并不存在任何消费者,那么这条消息将不会存入队列。当与路由键匹配的所有队列都没有消费者时,该消息会通过Basic.Return
返回给生产者。
概括来说,
mandatory
参数告诉服务器至少将该消息路由到一个队列中,否则将消息返回给生产者。immediate
参数告诉服务器,如果该消息路由的目标队列上有消费者,则立即投递;如果所有匹配的队列上都没有消费者,则直接将消息返回给生产者,不用将消息存入队列而等待消费者了。
2. 备份交换器
备份交换器的作用是指当生产者在发送消息的时候如果不设置mandatory
参数或者设置为false时,那么消息在未被路由的情况下可以使用备份交换器将消息存储起来,在未来需要的时候再去处理这些消息。
// 创建备份交换器
channel.exchangeDeclare("demo.alternate.exchange", "fanout", true, false, null);
channel.queueDeclare("demo.unrouted.queue", true, false, false, null);
channel.queueBind("demo.unrouted.queue", "demo.alternate.exchange", "");
// 创建普通交换器,并为其指定备份交换器
Map<String, Object> arguments = new HashMap<>();
arguments.put("alternate-exchange", "demo.alternate.exchange");
channel.exchangeDeclare("demo.direct.exchange", "direct", true, false, arguments);
channel.queueDeclare("demo.queue", true, false, false, null);
channel.queueBind("demo.queue", "demo.direct.exchange", "demo.routingKey");
// 往普通交换器发送消息
channel.basicPublish("demo.direct.exchange", "demo.routingKey.error",
MessageProperties.PERSISTENT_TEXT_PLAIN, "alternate exchange test message.".getBytes());
创建好备份交换器和普通交换器后,当我们发送一条无法被路由的消息时,则可以通过web管理界面看到消息被投递到demo.unrouted.queue
队列上来了。
我们可以看到,声明一个备份交换器和声明一个普通的交换器没有任何区别。但通常在使用场景上来说,备份交换器的类型建议设置成fanout
,即忽略绑定键将消息投递到每个队列。当然这里只是建议,并不是强制要求。需要注意的是,消息从普通交换器发送到备份交换器的路由键和从生产者发出的路由键是一样的。
考虑这样一种情况,如果备份交换器的类型为direct
,那么当一条消息投递到备份交换器但仍然无法投递到备份交换器所绑定的队列上,则消息丢失。
对于备份交换器,总结以下几种特殊情况:
(1)如果设置的备份交换器不存在,客户端和RabbitMQ服务端都不会有异常,此时消息丢失。
(2)如果备份交换器没有绑定任何队列,客户端和RabbitMQ服务端都不会有异常出现,此时消息丢失。
(3)如果备份交换器没有任何匹配的队列,客户端和RabbitMQ服务端都不会有异常出现,此时消息丢失。
(4)如果备份交换器和madatory
参数一起使用,那么madatory
参数无效。