Redis 实现延迟队列

Redis 做延迟队列有两种方案:

  • Redis 过期时间监听
  • Redisson 内置的延迟队列

Redis 过期事件监听实现延迟队列

Redis 有发布订阅(pub/sub)功能,在 pub/sub,引入了 channel(频道),类似于消息队列的 topic (主题)

pub/sub 涉及发布者publisher 和 订阅者 subscriber 两个角色

  • 发布者通过 publish 投递给指定的 channel
  • 订阅者通过 subscribe 订阅一个或多个的 channel

生产者指定消息到 channel,消费者订阅对应的 channel

Redis 中有很多默认的 channel,这些 channel 是由 Redis 本身向它们发送消息,__keyevent@0__:expired 就是一个默认的 channel,负责监听 key 的过期事件

当一个 key 过期之后,Redis 会发布一个 key 过期的事件到这个 channel 中

只需要监听这个 channel,就可以拿到过期的 key 的消息,进而实现了延迟队列

缺点 1:时效性差

过期事件是在 Redis 删除 key 时发布的,不是设置的过期时间到的时候直接发布

过期删除策略有 2 个

  • 惰性删除,对 CPU 友好,但是可能导致过期的 key 没有删除
  • 定期删除,每隔一段时间抽取一批 key 执行删除,对内存友好

Redis 采用的是 定期删除+惰性删除,因此会存在 key 过期了,但是事件还未发布

缺点2:丢消息

Redis 中消息不支持持久化,当 channel 没有订阅者时,消息会被直接丢弃

缺点3:多服务实例下存在消息重复问题

Redis 的消息队列只有广播模式,所有订阅指定 channel 的实例全都能收到消息,需要处理重复消费问题

Redisson 内置的延迟队列实现

Redisson的延迟队列 RDelayedQueue 是基于 ZSet 实现的,需要执行延迟的任务插入到 ZSet 中,并给他们设置相应的过期时间作为分数

使用 zrangebyscore 命令扫描 ZSet 中过期的元素,然后将这些过期元素从 ZSet 中移除,并将它们加入到就绪消息列表中。就绪消息列表是一个阻塞队列,有消息进入就会被监听到。这样做可以避免对整个 ZSet 进行轮询,提高了执行效率

优势:

  • 减少了丢消息的可能:DelayedQueue 中的消息会被持久化,即使Redis宕机了,根据持久化机制,也只可能丢失一点消息,影响不大。也可以使用扫描数据库的方法作为补偿机制。
  • 消息不存在重复消费问题:每个客户端都是从同一个目标队列中获取任务的,不存在重复消费的问题。

Redis 实现延迟队列
http://showyoubug.cn/2024/07/10/Redis 实现延迟队列/
作者
Dong Su
发布于
2024年7月10日
许可协议