一、背景

上一篇(灵感来袭,基于Redis的分布式延迟队列)讲述了基于Java DelayQueue和Redis实现了分布式延迟队列,这种方案实现比较简单,应用于延迟小,消息量不大的场景是没问题的,毕竟Java DelayQueue是占用内存的。针对现用方案的不足,于是利用Redis的Sorted Set数据结构简单实现分布式延迟队列。

二、Sorted Set

  • Redis 有序集合和集合一样也是String类型元素的集合,且不允许重复的成员。
  • 不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
  • 有序集合的成员是唯一的,但分数(score)却可以重复。

三、设计思路

redisson 延迟队列 集群 重复消费_redis延迟队列 实现

  1. 使用Redis的Sorted Set作为中转队列,为防止延迟消息量过大,维护多个Sorted Set,将延迟消息按照hash值平均分布到不同的Sorted Set中。
  2. 由于Sorted Set本身具备有序性,将延迟时间作为score值和延迟消息绑定到一起存入Sorted Set中。
  3. 另起Java定时任务,每隔一定时间扫描所有Sorted Set,并通过ZRANGEBYSCORE操作取出符合条件的延迟消息,然后放入目标队列等待消费者消费。

四、代码实现

4.1、延迟队列创建

redisson 延迟队列 集群 重复消费_延迟时间_02

根据queueName分别创建中转队列(Sorted Set)和 目标队列key值,其中queueSize是中转队列的大小。

4.2、延迟消息投递

redisson 延迟队列 集群 重复消费_延迟时间_03

根据延迟消息的hash值,平均分配到不同的中转队列(Sorted Set)中去。

4.3、中转定时任务

 

redisson 延迟队列 集群 重复消费_延迟时间_04

通过分布式锁来锁定唯一的线程来执行延迟消息迁移到目标队列的操作。遍历全部的中转队列,因为延迟消息是和延迟时间戳关联的,使用ZRANGEBYSCORE命令,取出延迟时间小于当前时间的50条消息并通过LPUSH命令放入目标队列里。

4.4、延迟消息消费

redisson 延迟队列 集群 重复消费_Java_05

 通过RPOP命令不断的从目标队列获取延迟消息,执行相应的消费逻辑。

五、总结

本文描述的实现方案还有诸多异常情况尚未考虑,比如生产者发送失败、消费者消费失败的情况,无法保证极端情况下生产者和消费者两端的数据一致性。该方案可以满足业务量不是很大、延迟时间较长、允许部分数据可能丢失的场景,比如用户签到提醒,可以根据用户签到的时间,第二天在相应的时间点推送消息提醒用户继续签到。