实现延迟队列的几种途径

20 篇文章 0 订阅
3 篇文章 0 订阅

什么是延迟队列?

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

应用场景

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

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

为什么要使用延迟队列

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

实现延迟队列的几种途径

  • Quartz 定时任务

  • DelayQueue 延迟队列

  • Redis sorted set

  • Redis 过期键监听回调

  • RabbitMQ死信队列

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

  • wheel时间轮算法

1.Quartz定时任务

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

导入Quartz依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

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

@SpringBootApplication
@EnableScheduling
public class DelayQueueApplication {
    public static void main(String[] args) {
        SpringApplication.run(DelayQueueApplication.class, args);
    }
}

编写定时任务

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

2.DelayQueue 延迟队列

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

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

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

DelayQueue<E extends Delayed>的队列元素需要实现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(一个分布式数据库)中,随后检测消息延迟时间,如达到投递时间将其通过类型标记的交换机投至目标队列。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值