• 实现延迟队列的几种途径


    什么是延迟队列

    延时队列相比于普通队列最大的区别就体现在其延时的属性上,普通队列的元素是先进先出,按入队顺序进行处理,而延时队列中的元素在入队时会指定一个延迟时间,表示其希望能够在经过该指定时间后处理。从某种意义上来讲,延迟队列的结构并不像一个队列,而更像是一种以时间为权重的有序堆结构。

    应用场景

    我们在一些业务场景中,经常会遇到一些需要经历一段时间后,或者到达某个时间节点才会执行的功能。就比如以下这些场景:

    • 新建一个订单,在规定时间内未支付需要自动取消
    • 外卖或者打车在预计时间到达的前十分钟提醒骑手或者司机即将超时
    • 快递收货后在规定时间内用户没有确认收货会自动确认收货
    • 预定的会议在会议开始前十分钟会去提醒你尽快加入会议
    • 每日周报在截止半小时前会提醒你尽快提交

    为什么要使用延迟队列

    对于一些数据量小并且对数据的时效性不怎么要求的项目来说,最简单有效的方法就是写一个定时任务去扫描数据库以达到业务的实现。当然,如果在数据达到数百万或者千万级别的时候,如果去定时扫描数据库,容易挨揍哈。想信大家也有所了解,当数据达到这种地步的时候,还去定时扫表会非常低效,甚至对于那些定时间隔比较小的情景来说,这一遍还没扫完下一遍就要开始了。这时候如果用延迟队列的话或许会很有效。

    实现延迟队列的几种途径

    • Quartz 定时任务

    • DelayQueue 延迟队列

    • Redis sorted set

    • Redis 过期键监听回调

    • RabbitMQ死信队列

    • RabbitMQ基于插件实现延迟队列

    • wheel时间轮算法

    1.Quartz定时任务

    Quartz一款非常经典任务调度框架,在Redis、RabbitMQ还未广泛应用时,超时未支付取消订单功能都是由定时任务实现的。

    导入Quartz依赖

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-quartzartifactId>
    4. dependency>

    在启动类中使用@EnableScheduling注解开启定时任务功能。

    1. @SpringBootApplication
    2. @EnableScheduling
    3. public class DelayQueueApplication {
    4. public static void main(String[] args) {
    5. SpringApplication.run(DelayQueueApplication.class, args);
    6. }
    7. }

    编写定时任务

    1. @Slf4j
    2. @Component
    3. public class QuartzDemo {
    4. /**
    5. * 每隔五秒开启一次任务
    6. */
    7. @Scheduled(cron = "0/5 * * * * ? ")
    8. public void process(){
    9. log.info("--------------定时任务测试--------------");
    10. }
    11. }

    2.DelayQueue 延迟队列

    DelayQueue是一个无界阻塞队列,只有在延迟期满时,才能从中提取元素。 队列的头部,是延迟期满后保存时间最长的delay元素。

    DelayQueue提供了在指定时间才能获取队列元素的功能,队列头元素是最接近过期的元素。

    没有过期元素的话,使用poll()方法会返回null值,超时判定是通过getDelay(TimeUnit.NANOSECONDS)方法的返回值小于等于0来判断。

    DelayQueue的队列元素需要实现Delayed接口,所以DelayQueue的元素需要实现getDelay方法和Comparable接口的compareTo方法,getDelay方法来判定元素是否过期,compareTo方法来确定先后顺序。

    所以,我们只需要去获取DelayQueue的头部元素,你能取出来的,都是已经过期的元素。

    3.Redis sorted set

    在Redis中,zet作为有序集合,可以利用其有序的特性,将任务添加到zset中,将任务的到期时间作为score,利用zset的默认有序特性,获取score值最小的元素(也就是最近到期的任务),判断系统时间与该任务的到期时间大小,如果达到到期时间,就执行业务,并删除该到期任务,继续判断下一个元素,如果没有到期,就sleep一段时间(比如1秒),如果集合为空,也sleep一段时间。

    4.Redis 过期键监听回调

    同样通过redis来实现,开启redis的过期键监听,每当key过期就会触发一个事件,达到延迟队列的效果。 首先要去开启redis的过期键监听,修改redis.conf文件,打开notify-keyspace-events Ex的注解

     

    其次,配置redis监听器 最后,编写redis key过期监听回调方法

    5.RabbitMQ死信队列

    利用RabbitMQ,也就是消息中间件去实现延迟队列是一种比较常见的事情,但是RabbitMQ并没有直接提供延迟队列的功能,而是通过DLX(死信交换机)+TTL(过期时间)去实现的。 一般来说,producer将消息投递到queue中,consumer从queue取出消息进行消费,但某些时候由于特定的原因导致queue中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信(Dead Letter),所有的死信都会放到死信队列中。 “死信”消息会被RabbitMQ进行特殊处理,如果配置了死信队列信息,那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃。 而RabbitMQ就是可以通过这个死信去实现延迟队列的效果。

    主要流程: 生产者生产消息——>到交换机分发给对应的队列——>过期后到死信交换机——>消费者进行消费

     

    6.RabbitMQ基于插件实现延迟队列

    安装插件后会生成新的Exchange类型 x-delayed-message ,而这一类型的交换机支持延迟投递机制,接收消息后并未立即将消息投递至目标队列,而是存储在mnesia(一个分布式数据库)中,随后检测消息延迟时间,如达到投递时间将其通过类型标记的交换机投至目标队列。

  • 相关阅读:
    【新刊邀稿】1区计算机类SCI,极速审稿,最快仅1个月25天录用,录用后16天见刊
    Django用RSA实现Web登录加密传输,预防抓包泄漏密码,解决ModelForm无法实现传输加密问题
    DIVFusion_ Darkness-free infrared and visible image fusion 论文解读
    企业怎么优化固定资产管理
    Python邮件发送程序代码
    AI企业盈利与成本问题
    【ESP32】制作 Wi-fi 音箱(HTTP + I2S 协议)
    数据分析 - 分散性与变异的量度
    【JavaSE】基础笔记 - 类和对象(下)
    java计算机毕业设计web网上办公自动化系统MyBatis+系统+LW文档+源码+调试部署
  • 原文地址:https://blog.csdn.net/TiankkTT/article/details/127828196