共计 1751 个字符,预计需要花费 5 分钟才能阅读完成。
一、利用场景
在需要开发过程中,咱们常常会遇到一些相似上面的场景:
a. 外卖订单超过 15 分钟未领取,主动勾销
b. 应用抢票软件订到车票后,1 小时内未领取,主动勾销
c. 待处理申请超时 1 天,告诉审核人员经理,超时 2 天告诉审核人员总监
d. 客户预约自若房子后,24 小时内未领取,房源主动开释
那么针对这类场景的需要应该如果实现呢,咱们最先想到的个别是启个定时工作,来扫描数据库里符合条件的数据,并对其进行更新操作。
一般来说 spring-quartz、elasticjob 就能够实现,甚至本人写个 Timer 也能够。然而这种形式有个弊病,就是须要不停的扫描数据库,如果数据量比拟大,并且工作执行间隔时间比拟短,对数据库会有肯定的压力。另外定时工作的执行间隔时间的粒度也不太好设置,设置长会影响时效性,设置太短又会减少服务压力。咱们来看一下有没有更好的实现形式。
java 全套视频学习材料:http://www.atguigu.com/download.shtml
二、JDK 延时队列实现
DelayQueue 是 JDK 中 java.util.concurrent 包下的一种无界阻塞队列,底层是优先队列 PriorityQueue。对于放到队列中的工作,能够依照到期工夫进行排序,只须要取曾经到期的元素解决即可。
具体的步骤是,要放入队列的元素须要实现 Delayed 接口并实现 getDelay 办法来计算到期工夫,compare 办法来比照到期工夫以进行排序。
应用 DelayQueue, 只须要有一个线程一直从队列中获取数据即可,它的长处是不必引入第三方依赖,实现也很简略,毛病也很显著,它是内存存储,对分布式反对不敌对,如果产生单点故障,可能会造成数据失落,无界队列还存在 OOM 的危险。
三、工夫轮算法实现
1996 年 George Varghese 和 Tony Lauck 的论文《Hashed and Hierarchical Timing Wheels: Data Structures for the Efficient Implementation of a Timer Facility》中提出了一种工夫轮治理 Timeout 事件的形式。其设计十分奇妙,并且相似时钟的运行,如下图的原始工夫轮有 8 个格子,假设指针通过每个格子破费工夫是 1 个工夫单位,以后指针指向 0,一个 17 个工夫单位后超时的工作则须要运行 2 圈再通过一个格子后被执行,放在雷同格子的工作会造成一个链表。
相比 DelayQueue 的数据结构,工夫轮在算法复杂度上有肯定劣势,但用工夫轮来实现延时工作同样防止不了单点故障。
四、Redis ZSet 实现
Redis 里有 5 种数据结构,最罕用的是 String 和 Hash,而 ZSet 是一种反对按 score 排序的数据结构,每个元素都会关联一个 double 类型的分数,Redis 通过分数来为汇合中的成员进行从小到大的排序,借助这个个性咱们能够把超时工夫作为 score 来将工作进行排序。
应用 zadd key score member 命令向 redis 中放入工作,超时工夫作为 score, 工作 ID 作为 member, 应用 zrange key start stop withscores 命令从 redis 中读取工作,应用 zrem key member 命令从 redis 中删除工作。
相比前两种实现形式,应用 Redis 能够将数据长久化到磁盘,躲避了数据失落的危险,并且反对分布式,防止了单点故障。
五、MQ 延时队列实现
以 RabbitMQ 为例,它自身并没有间接反对延时队列的性能,然而通过一些个性,咱们能够达到实现延时队列的成果。
RabbitMQ 能够为 Queue 设置 TTL,,到了过期工夫没有被生产的音讯将变为死信——Dead Letter。咱们还能够为 Queue 设置死信转发 x-dead-letter-exchange,过期的音讯能够被路由到另一个 Exchange。下图阐明了这个流程,生产者通过不同的 RoutingKey 发送不同过期工夫的音讯,多个队列别离生产并产生死信后被路由到 exe-dead-exchange,再有一些队列绑定到这个 exchange,从而进行不同业务逻辑的生产。
应用 MQ 实现的形式,反对分布式,并且音讯反对长久化,在业内利用比拟多,它的毛病是每种间隔时间的场景须要别离建设队列。
关键词:java 培训