用Redis集群搞定那些零散又复杂的定时任务,效率还挺不错的感觉
- 问答
- 2026-01-24 20:13:38
- 3
用Redis集群搞定那些零散又复杂的定时任务,效率还挺不错的感觉
(根据技术社区分享内容整理)

以前我们处理定时任务,特别是那些零零散散、时间不固定、类型还特别多的任务,真是头疼,用数据库轮询吧,怕把数据库拖垮;用单机的定时任务框架吧,又怕机器一挂全完蛋,而且扩容也麻烦,后来我们琢磨着换了个思路,用Redis集群来搞,试了一段时间,感觉还挺顺手的。
最开始的想法其实挺简单,我们就把每个定时任务当成一条消息,它该执行的时间点就是它的“触发时间”,我们把这条消息塞进Redis里,并给它设置一个过期时间,这个过期时间就是任务需要延迟执行的时间,比如一个任务要5分钟后执行,我们就放一条内容为任务ID的数据,设置5分钟过期,然后我们起一个后台服务,专门监听Redis的key过期事件,一旦key过期被删除了,后台服务就能通过订阅的通道收到这个事件通知,知道对应任务时间到了,该去执行了,这个做法,社区里常有人讨论,算是用Redis实现延迟队列的一种常见玩法。

但真用起来,马上就发现坑了,最大的问题是,Redis的key过期事件通知(Keyspace Notifications)其实不是完全可靠的,它本身是一个发布/订阅机制,如果客户端在key过期的时候刚好没连着,或者订阅断了,那这个事件就彻底丢了,任务也就再也不会被执行了,这肯定不行啊,任务丢了可是大事。
所以我们做了个“双保险”的主从备份机制,我们不再只依赖过期事件,我们同时用了Redis的有序集合(Sorted Set),每次发布一个定时任务,我们做两件事:第一,像刚才说的,把一个临时的key设好过期时间;第二,把这个任务的ID和执行时间戳(比如5分钟后的时间戳)作为一个成员(member),它的分数(score)就是这个时间戳,存到一个有序集合里,这样,有序集合里就按时间顺序排好了所有待执行的任务,后台服务除了监听事件,还会每隔一段时间(比如30秒)去有序集合里扫描一次,用ZRANGEBYSCORE命令把当前时间之前的所有任务都捞出来处理,这样,即使偶尔丢失一两个过期事件,通过定时扫描也能把任务补回来,保证了任务至少被执行一次,这里就要处理一下重复执行的问题,我们的办法是在任务真正执行时,用一个简单的“处理中”状态锁来防重。

任务多了,单个Redis实例和单个后台服务处理不过来,我们就得上集群和分片,用Redis集群模式,把数据和压力分散开,任务分片是根据任务ID的哈希值取模,分配到不同的集群节点上,我们的后台处理服务也启动多个实例,每个实例只负责处理分配到特定Redis集群节点上的任务,这就有点像很多个工人,每人守着自己面前的一条传送带(Redis节点)干活,互不干扰,效率自然就上去了,这里有个细节要注意,就是订阅key过期事件的时候,每个服务实例需要连接到它负责的那个集群节点上去订阅。
我们还遇到一些特别“磨人”的任务,比如一个订单如果30分钟未支付就要自动关闭,这种任务数量巨大,而且时间点相对集中,如果每个订单都单独往Redis里塞一条数据,对内存压力不小,我们优化了一下,对这种海量同类型任务做了“聚合”,我们把未来一分钟内要关闭的订单ID先批量存到数据库里,然后在Redis里只设置一个代表“执行关闭任务”的key,让它一分钟后过期,后台服务收到事件后,再去数据库里批量取出这一批订单ID来处理,这样Redis里存的东西就少多了,非常省内存。
监控和补偿也不能少,我们在管理后台能看到每个任务的状态(等待中、处理中、已完成、失败),也会记录日志,对于扫描机制发现但处理失败的任务,会放到一个重试队列里,过会儿再试几次。
搞了这么一套下来,感觉确实轻快了不少,Redis集群本身性能高,撑住了我们每天海量的定时任务调度;通过集群和分片,水平扩展也容易,机器不够了就加节点、加处理服务实例;双保险的机制让任务基本不会丢,虽然中间件本身不是为定时任务设计的,需要自己搭一些逻辑,但灵活度也正在于此,可以根据自己业务的特点去调整,现在回过头看,这种“用缓存中间件干调度活”的思路,对付那些零散、不规则、量又大的定时任务,还真是个挺不错的感觉。
本文由召安青于2026-01-24发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://haoid.cn/wenda/85290.html
