关于延时任务,在业务场景中实在是太常见了。比如订单,下单 xx 分钟未支付就要将订单关闭。比如红包, XX 分钟未抢,则红包失效。
那么说起延时任务的实现方案的话,可能有很多人第一时间会想到轮询,即设置定时任务,而稍有经验的开发者就知道。轮询这机制会给数据库带来很大压力,小业务当然无所谓。如果是大量数据要处理的业务用轮询肯定是不行的。而且你如果要保证高可用,就又得牵扯出分布式定时任务。怎么搞都很麻烦。
很多小机灵鬼知道可以用消息队列来实现。确实,MQ 的异步性和解耦性在延时任务的这种场景下可以爆发出很强的战斗力。而 RabbitMQ 因其被广泛使用,关于如何实现延时任务自然也有其解决方案。
下面本文基于 SpringBoot 环境演示一下使用 RabbitMQ 实现延时任务的方案
用文字和 UML 活动图来讲一讲所谓 RabbitMQ 的 “死信” 机制如何实现延时消息的需求及其功能上的 不足
1、死信是什么
说起死信,balabala 的什么死信队列、死信交换机这种名词就出来了。
这个词语有点抽象,但也不是那么难以理解。死信死信,就当他死了~
比如你生产者发送一条消息到 MQ Broker ,这条消息因为各种原因没被消费掉,消息最终挂掉了 / 死了。就可以认为他是死信
那么死信队列呢?死信交换机呢?其实这两个东西 和普通的队列、交换机是一样的,并没有本质区别
不过可以通过对 RabbitMQ 的配置,将其设置为 “死信” 的处理者。就是一条消息因为种种原因没被消费掉,最终死了,那么就把这个消息转发给死信交换机、由他来对这个死亡的消息进行处理
这种设置、处理 在 RabbitMQ 中是点对点的,即一个普通队列 可以绑定一个死信交换机。
指定队列的死信交换机需要设置队列的属性参数 (arguments)
具体参数名:
绑定死信交换机 : x-dead-letter-exchange
路由死信时使用的 routingkey : x-dead-letter-routing-key
2、什么情况会产生死信
在 RabbitMQ 中,产生死信有这么几种情况
1、队列长度满了
2、消费者拒绝消费消息 (丢弃)
3、消息 TTL 过期
这里说到了 TTL ,那么就需要解释一下这是个什么东西。
TTL 是 time to live 的缩写,即生存时间。
RabbitMQ 中可以在队列上、单条消息上设置 TTL。如果是设置在队列上,则可以认为该条队列中所有消息的 TTL 为设定值。
队列 TTL 属性参数: x-message-ttl
单条消息 TTL 参数: expiration
如果设置了 TTL 值,消息待在队列中的时间超过 TTL 值后还未被消费的话,消息队列则会将消息丢弃,产生” 死信”。
产生死信后,若队列配置了死信交换机,则会将消息流转到绑定的死信交换机中,然后再由死信交换机路由到死信队列。
死信队列再推送给这个队列的消费者
3、基于死信机制的延时任务实现方案
那么,根据上述 1、2 知识点,对应的延时任务实现方案自然就出来了。
具体方案:
1、创建一个没有消费者的队列,设置 TTL 值,并绑定死信交换机
2、所有需要延时的消息全部向这条队列发送。
3、死信交换机绑定对应的死信队列,其消费者即为处理延时消息的服务
根据以上方案逻辑,在发消息到队列后,必定会等待到消息过期后——即指定的延时时间后,才会有消费者对消息进行处理。
可以实现延时任务的需求。
活动图如下所示:
3、Spring 中 RabbitMQ 死信实现方式
既然知道了原理和机制&