一般实现的方法有:

  • rocketMQ、rabbitMQ、pulsar等消息队列的延时投递功能;
  • redisson提供的DelayQueue;
  • redisson delayQueue是一种基于redis zset结构的延时队列实现,delayQueue中有一个名为 timeoutSetName 的有序集合,其中元素的score为投递时间戳。delayqueue会定时使用zrangebyscore 扫描已到投递时间的消息,然后把它们移动到就绪消息列表中。
  • delayQueue在保证redis不崩溃的情况下不丢失消息

有问题的实现方法:

redis过期监听:

实现方式:

  • 定时任务离线扫描并删除部分过期键;在访问建时惰性检测是否过期并删除过期键;

缺点:

  1. redis从未保证会在设定的过期时间立即删除并发送过期通知,过期通知晚于设定的过期时间数分钟的情况都比较常见;
  2. 过期通知采用发送即忘(fire and forget)策略,并不会像消息队列一样保证送达。订阅事件的客户端可能会丢失所有在断线期间所有发送给它的事件。

rabbitMq死信队列:

实现方式:

  1. 死信是rebbitMQ提供的一种机制;当一条消息满足一定条件会被认定为死信,死信会被投入到死信队列中。死信队列设计的目的是为了存储未被正常消费的消息,便于排除和重新投递
  1. 满足条件以下任一条件成为死信:
  1. 消息被否定确认(如clannel.basicNack),并且此时requeue属性被设置为false;
  2. 消息在队列的存活时间超过设置的TTL时间;
  3. 消息队列的消息数量已经超过最大队列长度;
  1. 在rabbitMq中创建死信队列的流程
  1. 创建一个交换机作为死信交换机;
  2. 在业务队列中配置x-dead-letter-exchange  和  x-dade-letter-routing-key 将第一步的死信交换机设为业务队列的交换机;
  3. 在死信交换机上创建队列,并监听此队列;

缺点:

  1. 死信队列同样不保证投递时间的即时性。
  2. 在第一条消息成为死信后,后面的消息即使过期也不会投递为死信。

建议:

  1. rabbit公司提供了延时投递插件 rabbitMq-delayed-message-exchange

使用非持久化的时间轮

实现方式:

  1. 时间轮本身就是一种定时任务的数据结构。

缺点:

  1. 绝对大多数时间轮都是纯内存,没有持久化的,运行时间轮的进程崩溃后,该时间轮内所有的任务都会灰飞烟灭。