RabbitMQ 延时队列(实现定时任务)
1、定时任务的场景
购物下单等待支付的订单,超过一定时间后,系统就会自动取消订单并释放占有的资源。
常用的解决方案是:使用 Spring 的 Schedule 定时任务轮询数据库。但缺点很大:消耗系统内存、增加了数据库的压力、存在较大的时间误差。
较好的解决方案是使用 RabbitMQ 的延时队列:RabbitMQ 消息的 TTL 和死信 Exchange 结合。
2、消息的 TTL(Time To Live)
消息的 TTL 就是消息的存活时间。RabbitMQ 可以对队列和消息分别设置 TTL:
- 对队列设置 TTL 就是队列没有消费者连着的保留时间,也可以对每个消息单独设置。超过了这个时间,我们就认为这个消息死了,这种消息称之为死信,而保存死信的队列就被称之为死信队列。
- 如果同时对队列和消息设置,那么会取小的。所以一个消息如果被路由到不同的队列中,这个消息的死亡时间有可能不一样。
在这里,只有设置单个消息的 TTL 才是实现延时任务的关键。可以通过设置消息的 expiration
字段或者 x-message-ttl
属性来设置时间,两者效果一样的。
3、死信路由交换机 - Dead Letter Exchange(DLX)
一个消息在满足如下条件时会被放入到死信路由中(什么是死信):
- 一个消息被 Consumer 拒收了,并且 reject 方法的参数里 requque 是 false,也就是说不会被再次放入到队列中被其它消费者消费;
- 消息的 TTL 到了,消息过期了;
- 队列的长度限制满了,排在前面的消息会被丢弃或者扔到私信路由上。
Dead Letter Exchange 其实就是一种普通的 exchange,和其他的 exchange 没有两样,只是在某一个设置 Dead Letter Exchange 的队列中有消息过期了,会自动触发消息的转发,发送到 Dead Letter Exchange 中去。
我们既可以控制消息在一段时间后变成私信,又可以控制死信的信息被路由到某一个指定的交换机中,结合二者就可以实现一个延时队列。
4、延时队列原理
其工作原理和流程如下:
- 延时队列
order-delay-queue
和消费者监听的队列order-release-queue
同时和order-event-exchange
交换机绑定,延时队列和交换机的路由键为order-create
,消费者队列的路由键为order-release-queue
; - 生产者发送消息到 exchange 中,路由键设置为
order-create
,然后交换机把消息投递到延时队列中; - 由于消息设置了超时时间
x-message-ttl
为 60 秒,死信队列路由交换机为order-event-exchange
,死信路由键为order-release
,所以当存放的消息到达指定的 60 秒后,就会变成私信并投递到指定的死信路由交换机中,这样,消息又重新回到了的了order-event-exchange
交换机中; - 由于死信路由键已经重新设置为了
order-release
,所以,交换机再次根据该路由把消息投递到order-release-queue
队列中,最后交给消费者处理。
在整个流程中,我们服用了 order-event-exchange
交换机,而延时队列只作为存放延时消息的队列不被任何消费者监听。
5、延时队列应用案例
根据上面的原理和流程,下面我们来编写一个延时队列的案例:
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
@Configuration
public class MyRabbitMQConfig {
// 把 Queue、Exchange、Binding 对象的创建交给 Spring 管理
// 创建交换机
@Bean
public Exchange orderEventExchange() {
/* 交换机类型为 TopicExchange
* String name,
* boolean durable,
* boolean autoDelete,
* Map<String, Object> arguments
*/
return new TopicExchange("order-event-exchange", true, false);
}
// 创建死信队列
@Bean
public Queue orderDelayQueue() {
/*
* Queue(String name, 队列名字
* boolean durable, 是否持久化
* boolean exclusive, 是否排他
* boolean autoDelete, 是否自动删除
* Map<String, Object> arguments) 属性
*/
HashMap<String, Object> arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange", "order-event-exchange");
arguments.put("x-dead-letter-routing-key", "order.release");
arguments.put("x-message-ttl", 60000); // 消息过期时间 1分钟
Queue queue = new Queue("order.delay.queue", true, false, false, arguments);
return queue;
}
// 消费者队列
@Bean
public Queue orderReleaseQueue() {
Queue queue = new Queue("order.release.queue", true, false, false);
return queue;
}
// 绑定延时队列和交换机
@Bean
public Binding orderCreateBinding() {
/*
* String destination, 目的地(队列名或者交换机名字)
* DestinationType destinationType, 目的地类型(Queue、Exhcange)
* String exchange,
* String routingKey,
* Map<String, Object> arguments
* */
return new Binding("order.delay.queue", Binding.DestinationType.QUEUE,
"order-event-exchange", "order.create", null);
}
// 绑定消费者队列和交换机
@Bean
public Binding orderReleaseBinding() {
return new Binding("order.release.queue", Binding.DestinationType.QUEUE,
"order-event-exchange", "order.release.order", null);
}
}