用Redis锁搞高并发,超时过期怎么整才靠谱一点
- 问答
- 2025-12-23 14:37:08
- 2
用Redis锁来处理高并发,核心目标就是保证在同一时间,只有一个请求能执行那段关键的代码,这就像一群人去抢一个单人间厕所,锁就是厕所的门栓,一个人进去后从里面栓上,别人就只能在外面等着,而“超时过期”就是怕进去的人晕倒在里面了,外面的人永远也进不去,所以设置一个自动开门的时间,比如10分钟,超过10分钟门自动打开,让下一个人有机会进去。
这个“超时过期”机制听起来简单,但里面坑特别多,搞不好就会导致锁失效或者把场面弄得一团糟,想弄得靠谱一点,得从几个方面下手。
设置锁和设置过期时间必须是一个原子操作,这是最最基础的一条,但很多新手会栽在这里,你不能先执行SET key value把锁锁上,然后再执行EXPIRE key timeout去设置过期时间,因为如果在这两条命令执行的间隙,Redis宕机了或者你的应用进程崩溃了,那么锁就永远被锁住了,成了“永生锁”,其他请求再也进不来了,这就是灾难,必须用一条原子命令完成设置锁和过期时间,在Redis中,正确的方法是使用SET key value NX PX timeout这个命令。NX表示只有当key不存在时才能设置成功(也就是抢锁),PX后面跟毫秒为单位的过期时间,这条命令能确保要么一起成功,要么一起失败,根据《Redis官方文档》对SET命令的说明,这是实现分布式锁的推荐方式。
过期时间设置多久是个学问,设得太短,可能你的业务代码还没执行完,锁就自动释放了,导致另一个请求也能进来,等于锁失效了,数据就可能出错,设得太长,万一持有锁的客户端因为某种原因(比如GC停顿、网络问题、进程崩溃)无法主动释放锁,其他请求就得白白等很久,系统吞吐量就下来了,一个比较靠谱的做法是:给你的业务逻辑设定一个合理的超时时间,然后Redis锁的过期时间要比这个业务超时时间再长一些,比如业务逻辑正常情况下2秒跑完,你可以把锁的过期时间设为5秒或10秒,留出足够的余量,你需要对自己的业务代码执行时间有一个基本的评估。
第三,也是最容易出问题的一点:只能释放自己加的锁,想象一下这个场景:请求A拿到了锁,设置过期时间是10秒,但是因为某些原因(比如遇到了Full GC),A的执行时间超过了10秒,锁被Redis自动释放了,这时请求B成功拿到了锁,紧接着,A的代码终于执行完了,它去执行释放锁的操作,如果它不加判断,就会把B刚创建的锁给释放掉!然后请求C又能进来了,系统就乱套了,在释放锁的时候,必须确保这把锁是自己当初创建的那把,具体做法是,在加锁时,value不要用一个简单的固定值(lock”),而应该用一个全局唯一的值,比如UUID或者请求ID,释放锁的时候,先用自己的唯一ID去GET一下锁的value,看看是不是和自己当初设置的一样,一样的话才允许删除,这里又有一个坑:判断value和删除锁这两个操作也必须是原子的,你不能先GET,判断是自己的锁,然后再DEL,因为可能在GET之后、DEL之前,锁过期了,并且又被其他请求抢去了,你又会误删别人的锁,为了解决这个问题,需要使用Lua脚本,Lua脚本在Redis中是原子执行的,可以确保判断和删除的连续性,脚本大概长这样:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
(根据《Redis官方文档》中关于分布式锁的指南,使用Lua脚本是确保释放锁操作安全性的关键步骤。)
第四,要考虑锁过期了但业务还没执行完的情况,即使我们设置了比较长的过期时间,仍然有可能因为某些极端情况(如系统STW时间过长)导致业务没执行完锁就没了,对于这种极端场景,一个更高级的解决方案是使用“看门狗”机制,简单说,就是另起一个后台线程,在业务持有锁期间,定期(比如在过期时间的三分之一时)去检查业务是否还在执行,如果还在执行,就自动给锁“续期”,重置一下过期时间,这样就能有效防止因为执行时间过长导致的锁意外过期,不过这个实现起来就复杂一些了,通常可以直接使用一些成熟的库,比如Redisson(一个Java的Redis客户端),它已经内置了这个功能。
别把Redis锁用成万能药,Redis锁在大多数场景下表现很好,但它并不是一个完美无缺的分布式锁,在Redis主从切换的极端情况下,可能会出现锁失效的问题(比如主节点挂了,锁还没来得及同步到从节点,从节点升级为主,新的请求又能拿到锁),如果你对锁的绝对强一致性有要求,可能需要考虑更复杂的方案,比如RedLock算法(Redis作者提出的算法,但也有争议),或者使用ZooKeeper、etcd等CP系统的锁,但对于绝大多数高并发业务场景,遵循上述几点用好Redis单实例锁,已经足够靠谱了。
想靠谱点,记住四条:1. 加锁和过期必须原子操作;2. 过期时间要设得比业务执行时间长,留足余量;3. 释放锁时要用Lua脚本保证只能删自己的锁;4. 对稳定性要求极高的场景,考虑用带“看门狗”的客户端库或者更高级的锁方案。

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