用Redis来锁定那些时间任务,避免重复执行的那些坑和经验分享
- 问答
- 2026-01-17 14:09:01
- 2
我们得明白为什么定时任务会需要锁定,想象一下,你有一个每五分钟跑一次的定时任务,用来处理积压的数据,这个任务被部署在一台服务器上,运行得很好,但后来为了高可用,你把这个服务部署到了三台服务器上,这下问题就来了:三台服务器的定时任务会在几乎相同的时间点被唤醒,然后同时去处理同一批数据,结果就是数据被重复处理,轻则浪费资源,重则导致业务数据错乱,比如同一笔订单被发货三次,这就是我们最需要避免的“重复执行”坑。(来源:常见的分布式系统并发问题)
这时候,Redis就派上用场了,因为它是一个独立于我们应用服务之外、能被所有服务器访问到的中心化存储,我们可以用它来充当一个“分布式锁”的公告板,核心思想很简单:哪个任务实例能成功在Redis里设置一个特殊的“钥匙”,哪个就获得执行权,其他实例看到钥匙已经存在,就放弃执行,默默退出。
但说起来简单,做起来坑很多,下面就是我总结的几个关键经验和容易踩的坑。
第一个大坑:锁忘了释放,导致任务“死锁”。
这是最常见的问题,任务A在Redis里设置了一个锁,然后开始执行,但执行过程中,程序抛了异常崩溃了,或者服务器突然重启了,导致任务A没有机会去删除这个锁,那么这个锁就会一直留在Redis里,后续所有的任务实例看到锁还在,就都不敢执行了,整个定时任务就彻底“瘫痪”了。
经验分享: 必须给锁设置一个过期时间,这样即使持有锁的任务崩溃了,超过这个时间后,Redis也会自动把锁删除,让其他任务有机会获得锁,在Redis中,设置锁和设置过期时间最好是一个原子操作,比如使用 SET key value NX PX 30000 这个命令,NX表示只有当key不存在时才设置,PX表示过期时间单位是毫秒,这个命令能确保“设置锁”和“设置超时”两步一起完成,避免刚设置好锁还没来得及设超时,程序就挂了的情况。(来源:Redis官方文档对SET命令NX、PX参数的说明)
第二个坑:误删了别人的锁。 任务A拿到了锁,设置的过期时间是30秒,但任务A可能因为某些原因执行得很慢,花了40秒,在它执行到第35秒的时候,锁因为过期时间到了被Redis自动删除了,此时任务B看没锁了,就成功地设置了新锁并开始执行,任务A在第40秒终于执行完了,它还很“负责任”地去执行删除锁的操作,结果就是把任务B刚设置的锁给删掉了!这会导致任务C可能趁机而入,又造成重复执行。 经验分享: 在删除锁的时候,要确保删除的是“自己”的锁,我们可以在设置锁的时候,将value设为一个唯一的值,比如一个UUID或者请求ID,在删除锁之前,先获取一下当前锁的值,如果这个值和自己当初设置的值匹配,才能删除,为了确保“获取锁值”和“删除”这两个操作的原子性(避免在获取值之后、删除之前锁过期又被别人设置),可以使用Lua脚本来执行这个逻辑,因为Lua脚本在Redis中是原子执行的。(来源:Redis官方对使用Lua脚本实现原子性复杂操作的建议)
第三个值得讨论的经验:锁的过期时间设多久? 这没有标准答案,是个权衡,设得太短,可能任务还没执行完锁就过期了,增加了上面“误删”风险发生的概率,设得太长,万一任务真的挂了,需要等待很长时间系统才能恢复。 经验分享: 这个时间应该基于你对任务正常执行时间的评估,并留出充足的余量,任务平时跑大概10秒,你可以设个30秒或60秒,更高级的做法是使用“看门狗”机制,也就是在任务执行期间,另起一个线程定期(比如每隔过期时间的1/3)去检查任务是否还在运行,并刷新锁的过期时间,这样只要任务没完,锁就一直有效,如果任务挂了,看门狗线程也挂了,锁最终还是会过期,但这实现起来就复杂一些了。(来源:一些开源分布式锁框架如Redisson的实现思想)
第四个点:要不要追求绝对强一致? 在某些极端情况下,比如Redis采用主从架构时,主节点写入锁成功后,在数据同步到从节点之前,主节点宕机了,此时哨兵机制会选举一个从节点成为新的主节点,但这个新主节点上可能还没有刚才那个锁的数据,另一个客户端来请求锁,又能成功获得,导致两个客户端同时持有锁。 经验分享: 对于绝大多数业务场景,比如发送提醒短信、生成日报、清理缓存等,我们其实不需要为此付出巨大的性能代价去追求那种百分百不会出错的强一致性锁,这种极端情况发生的概率很低,而且带来的业务影响通常是可以接受的(比如多发一条短信),如果你处理的场景是像“秒杀扣库存”这种对一致性要求极高的金融级场景,那你可能需要考虑更复杂的分布式锁方案,比如Redlock算法(Redis作者提出),但Redlock本身也有争议和实现复杂度,根据你的业务容忍度来选择合适的技术方案,避免过度设计。(来源:Redis作者Antirez关于Redlock算法的博客文章以及相关争议讨论)
用Redis锁定时任务,核心就是四句话:设置锁要原子操作并带过期时间;删除锁要检查所有权并用Lua脚本保证原子性;过期时间要合理设置并考虑续期;技术方案要匹配业务的实际需求,别用高射炮打蚊子。 把这些点注意到,就能避开大部分坑,让分布式环境下的定时任务安安稳稳地运行。

本文由芮以莲于2026-01-17发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://haoid.cn/wenda/82451.html
