前言
如果你还不知道什么是死信队列、什么是延迟队列、死信队列和延迟队列有什么关系,请参考我的这篇文章:RabbitMQ学习笔记之进阶篇.
如果你还不知道怎么在SpringBoot 2.x中配置和使用RabbitMQ,请参考我的这篇文章:在SpringBoot中使用RabbitMQ客户端详解.
延迟队列的基本原理
RabbitMQ没有直接提供延迟队列的功能,但是我们可以通过给队列设置消息过期时间+死信队列来模拟出延迟队列的功能。首先我们需要创建一个队列A,并给这个队列A设置消息过期时间,消息的过期时间即是我们需要延迟的时间。我们向队列A发送消息,但不要通过消费者消费队列A的消息,当队列A中的消息过期变成死信后,就会通过死信交换器进入到死信队列,我们客户端要消费的是死信队列的消息。
配置
我们需要一个设置了消息过期时间的队列以及和这个队列绑定了的交换器,还需要一个死信队列以及与这个死信队列绑定了的死信交换器。可以通过RabbitMQ提供的管理网页来配置,不过这里我选择通过代码来配置,因为这篇文章是接着我前言中提到的在SpringBoot中使用RabbitMQ客户端详解来写的,所以这里我就不贴完整代码了,如下是RabbitMQConfig的片段:
@Value("${rabbit.normalExchange.name}")
private String normalExName;
@Value("${rabbit.dlxExchange.name}")
private String dlxExName;
@Value("${rabbit.normalQueue.name}")
private String normalQueueName;
@Value("${rabbit.dlxQueue.name}")
private String dlxQueueName;
@Override
public void afterPropertiesSet() throws Exception {
AmqpAdmin amqpAdmin = amqpAdmin();
//创建正常交换器
Exchange normalExchange = new DirectExchange(normalExName);
amqpAdmin.declareExchange(normalExchange);
//创建死信交换器
Exchange dlxExchange = new DirectExchange(dlxExName);
amqpAdmin.declareExchange(dlxExchange);
//创建正常的队列
Queue normalQueue = new Queue(normalQueueName);
//设置消息超时时间
normalQueue.addArgument("x-message-ttl", 10000L);
//设置死信交换器
normalQueue.addArgument("x-dead-letter-exchange", dlxExName);
amqpAdmin.declareQueue(normalQueue);
//创建死信队列
Queue dlxQueue = new Queue(dlxQueueName);
amqpAdmin.declareQueue(dlxQueue);
String bindingKey = normalQueueName;
Binding dlxBinding = new Binding(
dlxQueueName,
DestinationType.QUEUE,
dlxExName,
bindingKey,
null);
amqpAdmin.declareBinding(dlxBinding);
Binding normalBinding = new Binding(
normalQueueName,
DestinationType.QUEUE,
normalExName,
bindingKey,
null);
amqpAdmin.declareBinding(normalBinding);
}
这里有一点需要注意的是,设置了超时时间的那个队列绑定交换器的BindingKey(或RoutingKey)需要跟死信队列绑定死信交换器的BindingKey一样,要不消息过期后能到达死信交换器,却无法进入死信队列。
当我们的项目启动完成后,就可以在RabbitMQ的管理控制台看到我们创建的队列信息,如下图:
上图中的queue.normal即为设置了消息过期时间的队列,"D"代表它是durable的,“TTL”代表它设置了消息过期时间,“DLX”代表它设置了死信交换器,将鼠标放在“DLX”上面还可以看到死信交换器的名字。
测试
在TestController中新增一个方法:
@GetMapping("/dlx")
public String testDlx(String msg) {
rabbitMQProducer.sendMsg(msg);
return "ok";
}
RabbitMQProducer新增一个方法:
public void sendMsg(String msg) {
//第二个参数是routingKey,因为跟队列名一样,所以用队列名代替
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(new Date()) + "发送消息->" + msg);
this.rabbitTemplate.convertAndSend(normalExName, normalQueueName, msg);
}
RabbitMQConsumer监听死信队列的消息:
@RabbitListener(queues = "${rabbit.dlxQueue.name}")
@RabbitHandler
public void receiveFromDlx(String msg, Channel channel, Message message) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(new Date()) + "收到消息->" + msg);
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
channel.basicAck(deliveryTag, false);
} catch (IOException ex) {
ex.printStackTrace();
}
}
通过对比消息发送时间和接收时间,发现两者确实是相差10秒,跟我们设置的一样。以上,就是在SpringBoot 2.x中使用RabbitMQ延迟队列的正确姿势。