介绍一下延迟队列的使用场景和简单实现。

使用场景

延迟队列在实际业务中有很多的使用场景:比如用户注册一定时间后,推送活动消息;订单下单一段时间后未付款,自动取消订单等。

和定时任务的区别

定时任务执行的周期是固定的,时间可以提前预知。延迟队列是当某个事件发生以后再延迟多久触发一定的操作,事件发生的时间不是固定的。

实现方式

Redis Zset实现

  1. 使用timestamp作为任务的score,每秒轮询score大于当前时间的key进行执行即可。
  2. 缺点是zset无法支持特别大的数据量。

RabbitMQ队列实现

  1. RabbitMQ有两个特性,一个是Time-To-Live Extensions,另一个是Dead Letter Exchanges。
  2. Time-To-Live Extensions允许我们为消息或者队列设置TTL(Time To Live),也就是过期时间,单位为毫秒。消息过期后成为Dead Letter。如果既配置了消息的TTL,又配置了队列的TTL,那么较小的那个值会生效。
  3. Dead Letter Exchanges在RabbitMQ中,一共有三种消息的 “死亡” 形式:
消息被拒绝
消息因为设置了TTL而过期
队列达到最大长度
  1. DLX同一般的 Exchange 没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性。当队列中有 DLX 消息时,RabbitMQ就会自动的将 DLX 消息重新发布到设置的 Exchange 中去,进而被路由到另一个队列,publish 可以监听这个队列中消息做相应的处理。
  2. RabbitMQ本身是不支持延迟队列的,但利用它的特性组合起来可以变相的实现延迟队列的功能。
  3. 缺点是绑定了RabbitMQ,如果要替换MQ比较麻烦。
  4. 另一个缺点是配置麻烦,需要额外增加一个死信交换和一个死信队列的配置。

时间轮实现

  1. 时间轮是一个环形结构,可以想象成时钟,分为很多格子,一个格子代表一段时间(越短Timer精度越高),并用一个List保存在该格子上到期的所有任务。
  2. 同时一个指针随着时间流逝一格一格转动,并执行对应List中所有到期的任务。