随着互联网的发展,越来越多的业务场景需要使用延迟队列。比如:
随着技术的进步,实现的方式也愈来愈多种多样,以 Java 语言为例,实现的方式也是多如牛毛,例如使用JDK字段得工具包实现;使用任务调度框架实现;使用Netty的时间轮询实现;使用消息队列实现;使用Redis 实现等等。本文主要探讨 Redis 实现延时消息的几种不同的方案演进。
如前文所述,使用 Redis 来实现延时消息的实现方式主要有 3 种
得益于 Redis 自身设计的优点,使用 Redis 完全可以支撑高性能的要求。
从可靠性和使用便利性上来考虑,三种方案优先级排序: Redisson > zset> 过期事件监听
Redis 的过期事件监听是基于 pub/sub 的,key 过期时会 pub 消息到一个内置的 channel 中,客户端可以通过监听这个 channel 获取到消息,:进而实现延时队列的功能
缺陷 1: 不支持持久化,可靠性低
pub/sub 模式下的过期事件监听,消息并不会做持久化,会有消息丢失的风险。
缺陷2:不保证及时性
过期事件监听的方案听起来很好理解,也似乎很完美,但事实并非如此。主要是借助 Redis 删除过期 key 的消息监听,不保证及时性
Redis 的单线程设计,如果需要支持定时过期,在 Redis 高负载的情况下或者有大量过期键需要同时处理时,会造成 Redis 服务器卡顿,影响主业务执行。考虑到可用性,Redis 的单线程机制并不能很好地支持定时删除过期 key 的场景,所以 Redis 使用惰性删除 + 定期删除key 的方式
也正因为 Redis key 的删除不是过期即删除的,所以 key 删除时发送消息的时间也不一定是 key的过期时间。
失效场景可以参考下图:
Redis 提供了有序集合 zset,我们也可以利用 zset 封装延迟消息
zset 的常用命令及解释
ZADD key score member [score member ...]: 向有序集合 key 中添加一个或多个成员,每个成员都带有一个分值 score
ZRANGEBYSCORE key min max [WITHSCORES]: 返回有序集合 key 中分值介于 min 和 max 之间的成员。可选的 WITHSCORES 参数表示同时返回成员和分值
ZREM key member [member ...]: 从有序集合 key 中移除一个或多个成员
核心设计为使用 zset + 定时轮询器,基于 zset 的 ZRANGEBYSCORE 命获取已过期的延时任务,流程如下图所示:
直接使用 zset 也存在一些端
缺陷 1: 额外的资源消耗
使用有序集合作为延时队列,并且需要定期地检查有序集合中的任务是否需要被处理,会占用CPU
资源2缺陷 2:使用上不够友好
需要自行封装,增加编码,在维护上增加了许多成本
基于 4.2 小节的考虑,我们可以考虑使用 Redisson 提供的延时队列,它也是基于 zset 实现
Redisson 封装的延时队列源码还是蛮多的,限于篇幅,之后看情况再补充源码分析篇。本文只讲解源码核心链路的封装。
Redisson 封装了两个核心队列: RBlockingQueue 和 RDelayedQueue,其中 RDelayedQueue 作为中间队列,RBlockingQueue 作为目标消费队列。