RabbitMQ知识点整理14-消息何去何从
mandatory和immediate是channel.basicPublish方法中的两个参数, 它们都有当消息传递过程中不可达目的地时将消息返回给生产者的功能, RabbitMQ 提供的备份交换器(Altemate Exchange) 可以将未能被交换器路由的消息(没有绑定队列或者没有匹配的绑定)存储起来,而不用返回给客户端。
关于mandatory和immediate接下来会详细说明...
mandatory 参数
当mandatory 参数设为true 时,交换器无法根据自身的类型和路由键找到一个符合条件的队列,那么RabbitMQ 会调用Basic.Return 命令将消息返回给生产者。当mandatory 参数设置为false 时,出现上述情形,则消息直接被丢弃。
那么生产者如何获取到没有被正确路由到合适队列的消息呢?这时候可以通过调用channel.addReturnListener来添加ReturnListener 监昕器实现。
使用mandatory 参数的关键代码如代码如下:
/** * channel.basicPublish的参数mandatory的使用 * * @author jiangkd * @date 2020/11/24 9:31 */ public class MandatoryTest { final private String EXCHANGE_NAME = "exchange_jkd_demo"; final private String QUEUE_NAME = "queue_jkd_demo"; @Test public void mandatoryTest() throws IOException, TimeoutException { // 关于连接rabbitmq, 自己封装了工具类, 不明白的请看之前的关于连接rabbitmq的教程说明 final Channel channel = ConnectUtil.channel(); // 定义交换器 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, false, null); // 定义队列 channel.queueDeclare(QUEUE_NAME, true, false, false, null); // bind, 绑定键是 "mandatory_test" channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "mandatory_test"); // 发送消息, 路由键空着, 让消息路由不到队列 // 设置参数 mandatory为true channel.basicPublish(EXCHANGE_NAME, "", true , MessageProperties.PERSISTENT_TEXT_PLAIN , "mandatory test".getBytes()); // 添加ReturnListener监听 channel.addReturnListener(new ReturnListener() { @Override public void handleReturn(int replyCode, String replyText , String exchange, String routingKey , AMQP.BasicProperties properties, byte[] body) { // String message = new String(body); System.out.println("Basic.Return返回的结果是:" + message); } }); } }
上面代码中生产者没有成功地将消息路由到队列,此时RabbitMQ 会通过Basic.Return返回" mandatory test " 这条消息, 之后生产者客户端通过ReturnListener 监昕到了这个事件, 最终的控制台输出如下:
Basic.Return返回的结果是:mandatory test
mandator参数图如下:
上面的示例只是演示了交换器路由不到队列, 如果存在 交换器1(direct) 绑定了 交换器2(fanout), 交换器2绑定了队列, 此时如果交换器1路由不到交换器2, 也会被监听到并返回给生产者, 示例如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * 测试参数mandatory, 当交换器路由不到交换器的时候 * * @throws IOException * @throws TimeoutException */ @Test public void mandatoryTest2() throws IOException, TimeoutException { // String exchange_name1 = "exchange_jkd_name1"; String exchange_name2 = "exchange_jkd_name2"; String queue_name = "queue_jkd_name1"; // final Channel channel = ConnectUtil.channel(); // 定义交换器1 channel.exchangeDeclare(exchange_name1, BuiltinExchangeType.DIRECT , true, false, false, null); // 定义交换器2 channel.exchangeDeclare(exchange_name2, BuiltinExchangeType.FANOUT , true, false, false, null); // 绑定两个交换器, 绑定键是 "exchange_bind_key" channel.exchangeBind(exchange_name2, exchange_name1, "exchange_bind_key"); // 定义队列 channel.queueDeclare(queue_name, true, false, false, null); // 绑定队列和交换器2, 交换器2传播类型是fanout, 无需指定绑定键 channel.queueBind(queue_name, exchange_name2, ""); // 发送消息, 路由键空着, 让消息路由不到交换器2 // 设置参数 mandatory为true channel.basicPublish(exchange_name1, "", true , MessageProperties.PERSISTENT_TEXT_PLAIN, "Helo~~~".getBytes()); // 添加ReturnListener监听 channel.addReturnListener(new ReturnListener() { @Override public void handleReturn(int replyCode, String replyText , String exchange, String routingKey , AMQP.BasicProperties properties, byte[] body) { // String message = new String(body); System.out.println("Basic.Return返回的结果是:" + message); } }); }
控制台输出如下:
Basic.Return返回的结果是:Helo~~~
immediate 参数
当immediate参数设置为true时, 如果交换器在将消息路由到队列时发现队列上并不存在任何消费者, 那么这条消息将不会存入队列中, 当与路由键匹配的所有队列都没有消费者时,该消息会通过Basic.Return 返回至生产者。
概括来说, mandatory 参数告诉服务器至少将该消息路由到一个队列(或者交换器)中, 否则将消息返回给生产者. imrnediate参数告诉服务器, 如果该消息关联的队列上有消费者,则立刻投递; 如果所有匹配的队列上都没有消费者,则直接将消息返还给生产者, 不用将消息存入队列而等待消费了.
备份交换器
备份交换器,英文名称为Altemate Exchange ,简称庙,或者更直白地称之为"备胎交换器"。生产者在发送消息的时候如果不设置mandatory参数, 那么消息在未被路由的情况下将会丢失; 如果设置了mandatory 参数, 那么需要添加ReturnListener 的编程逻辑, 生产者的代码将变得复杂, 如果既不想复杂化生产者的编程逻辑,又不想消息丢失, 那么可以使用备份交换器,这样可以将未被路由的消息存储在RabbitMQ 中,再在需要的时候去处理这些消息。
可以通过在声明交换器(调用channel.exchangeDeclare 方法)的时候添加alternate-exchange 参数来实现,也可以通过策略的方式实现。如果两者同时使用,则前者的优先级更高,会覆盖掉Policy 的设置。
Map<String, Object> args = new HashMap<String , Object>(); args.put("a1ternate-exchange" , "myAe"); channe1.exchangeDeclare( "normalExchange" , "direct" , true , fa1se , args); channe1.exchangeDec1are( "myAe" , "fanout" , true , fa1se , nu11) ; channe1.queueDeclare( "normalQueue" , true , fa1se , fa1se , nu11); channe1.queueBind("normalQueue" , "norma1Exchange" , " norma1Key"); channe1.queueDeclare("unroutedQueue" , true , fa1se , fa1se , nu11); channel.queueBind("unroutedQueue", "myAe", "");
上面的代码中声明了两个交换器nonnallixchange 和myAe ,分别绑定了nonnalQueue 和umoutedQueue 这两个队列,同时将myAe 设置为nonnallixchange 的备份交换器。注意myAe的交换器类型为fanout 。
如果此时发送一条消息到nonnalExchange 上,当路由键等于" normalKey" 的时候,消息能正确路由到nonnalQueue 这个队列中。如果路由键设为其他值,如"errorKey", 即消息不能被正确地路由到与nonnallixchange 绑定的任何队列上,此时就会发送给myAe ,进而发送到unroutedQueue 这个队列。
备份交换器其实和普通的交换器没有太大的区别,为了方便使用,建议设置为fanout 类型,如若想设置为direct 或者topic 的类型也没有什么不妥。需要注意的是,消息被重新发送到备份交换器时的路由键和从生产者发出的路由键是一样的。
考虑这样一种情况,如果备份交换器的类型是direct , 并且有一个与其绑定的队列,假设绑定的路由键是key1 , 当某条携带路由键为key2 的消息被转发到这个备份交换器的时候,备份交换器没有匹配到合适的队列,则消息丢失。如果消息携带的路由键为key1,则可以存储到队列中。
对于备份交换器,总结了以下几种特殊情况:
1.如果设置的备份交换器不存在,客户端和RabbitMQ 服务端都不会有异常出现,此时消息会丢失
2.如果备份交换器没有绑定任何队列,客户端和RabbitMQ 服务端都不会有异常出现,此时消息会丢失
3.如果备份交换器没有任何匹配的队列,客户端和RabbitMQ 服务端都不会有异常出现,此时消息会丢失
4.如果备份交换器和mandatory 参数一起使用,那么mandatory 参数无效。