目录
消息路由失败会怎样?
在发送消息channel.basicPublish方法中有两个参数分别是mandatory和immediate,它们都有当消息在传递过程中不可达目的地时,将消息返回给生产者的功能。RabbitMQ提供的备份交换器(Alternate Exchange)可以将未能被交换器路由的消息(没有绑定队列或者没有匹配的绑定)存储起来,而不用返回给客户端。
1、mandatory参数
当mandatory参数设为true时,如果交换器无法根据自身的类型和路由键找到一个符合条件的队列,那么RabbitMQ会调用Basic.Return命令将消息返回给生产者。当mandatory参数设置为false时,出现上述情形,则消息直接被丢弃。
public static void mandatory() throws Exception{
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 创建一个交换器
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 创建队列
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 将交换器与队列通过路由键绑定
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);
String message = "Hello World";
// mandatory = true
channel.basicPublish(EXCHANGE_NAME, "123", true,
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes());
// 添加监听器
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replyCode, String replyText, String exchange, String routingkey,
BasicProperties properties, byte[] body)
throws IOException {
System.out.println("exchange:" + exchange);
System.out.println("routingKey:" + routingkey);
System.out.println("生产者接收到服务器返回的路由失败消息:" + new String(body));
}
});
TimeUnit.SECONDS.sleep(5);
// 关闭资源
channel.close();
connection.close();
}
2、immediate参数
当immediate参数设为true时,如果交换器在将消息路由到队列时发现队列上并不存在任何消费者,那么这条消息将不会存入队列中。当与路由键匹配的所有队列都没有消费者时,该消息会通过Basic.Return返回至生产者。(这参数基本已经废弃,了解一下就行,不建议使用。官方说此方法影响了镜像队列的性能,增加了复杂度,所以将其取消;使用TTL+DLX代替,虽然不能完全代替,但是能实现它所需要做的功能)
3、备份交换器
备份交换器可以将未被路由的消息存储在 RabbitMQ 中,再在需要的时候去处理这些消息。
关于备份交换器:
(1) 如果设置的备份交换器不存在,客户端和RabbitMQ服务端都不会出现异常,此时消息会丢失。
(2) 如果备份交换器没有绑定任何队列,客户端和RabbitMQ服务端都不会出现异常,此时消息会丢失。
(3) 如果备份交换器没有任何匹配的队列,客户端和RabbitMQ服务端都不会出现异常,此时消息会丢失。
(4) 如果备份交换器和mandatory参数一起使用,如果备份交换器有效,那么mandatory参数无效 。
private static void main() throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
Map<String, Object> map = new HashMap<>();
map.put("alternate-exchange", EXCHANGE_NAME_BAK);
// 创建一个交换器 设置备份交换器
channel.exchangeDeclare(EXCHANGE_NAME, "direct", false, false, false, map);
channel.exchangeDeclare(EXCHANGE_NAME_BAK, "fanout");
// 创建队列
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
channel.queueDeclare(QUEUE_NAME_BAK, true, false, false, null);
// 将交换器与队列通过路由键绑定
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);
channel.queueBind(QUEUE_NAME_BAK, EXCHANGE_NAME_BAK, ROUTING_KEY);
// 消息RoutingKey与交换器绑定路由的BindingKey不匹配,消息路由失败
String message = "hello world";
channel.basicPublish(EXCHANGE_NAME, "123", true,
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes());
TimeUnit.SECONDS.sleep(5);
channel.close();
connection.close();
}
消息过期时间TTL
1.有两种方式可以设置消息的TTL:
-
通过设置队列属性值,队列中所有消息的过期时间都是相同的;
Map<String, Object> map = new HashMap<>(); map.put("x-message-ttl", 5000);// 消息过期时间 // 创建队列 channel.queueDeclare(QUEUE_NAME, true, false, false, map);
- 通过设置消息本身的过期时间,则队列中每条消息的TTL都可以不同。
private static void ttl() throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 创建一个交换器
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 创建队列
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 将交换器与队列通过路由键绑定
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);
AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties().builder();
builder.deliveryMode(2);// 持久化消息
builder.expiration("10000");// 设置消息超时时间,单位:ms
String message = "hello world";
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, builder.build(), message.getBytes());
TimeUnit.SECONDS.sleep(5);
channel.close();
connection.close();
}
如果两种方式一起设置,则消息的TTL以两者之间最小值为准。
注意:
- 对于第一种设置队列TTL属性的方法,一旦消息过期,就会从队列中抹去;而在第二种方法中,即使消息过期,也不会马上从队列中抹去。
- 队列中间位置的消息过期,RabbitMQ不会主动删除。
- 会定期扫描队列头部的消息,删除过期消息。
- 未删除的未过期消息自然不会进入死信队列。
死信队列
DLX,全称为Dead-Letter-Exchange,可以称之为死信交换器,也有人称之为死信邮箱。当消息在一个队列中变成死信(deadmessage)之后,它能被重新被发送到另一个交换器中,这个交换器就是DLX,绑定DLX的队列就称之为死信队列。
消息变成死信一般是由于以