Redis锁那些你可能没注意过的类型和区别,简单聊聊redis锁到底有哪些玩法
- 问答
- 2025-12-25 11:14:22
- 3
好,直接开始。
说到Redis锁,大部分人第一个想到的可能就是SETNX命令(SET if Not eXists),这确实是Redis锁最原始、最核心的玩法,但如果你以为Redis锁就这么简单,那可能就错过了很多更精细、更可靠的“高级玩法”,这些玩法都是为了解决在分布式环境下,用Redis实现锁时会遇到的各种各样的坑。
基础款:SETNX + DEL(手动挡)
这是最原始的模型,思路很简单:用一个特定的字符串作为锁的“钥匙”,客户端A想锁住一个资源,order_123”,就用SETNX lock_order_123 1,如果返回1,恭喜,抢锁成功,操作完成后,再用DEL lock_order_123把键删掉,相当于释放锁,让其他等待的客户端能继续抢。
但这个玩法问题一大堆,几乎不能在生产环境直接用,最大的问题是死锁:如果客户端A在执行业务逻辑时崩溃了,或者网络断了,那DEL命令永远没法执行,这个锁就永远不被释放,其他所有客户端都再也拿不到锁,资源就被永久锁死了。
升级款:SETNX + EXPIRE(给锁加个失效时间)
为了解决死锁问题,人们想出了给锁加一个过期时间(TTL),流程变成了:SETNX lock_order_123 1成功后,立刻执行EXPIRE lock_order_123 10,给这个锁设定10秒后自动过期,这样,即使客户端崩溃,锁最多占坑10秒,之后会自动释放,避免了永久死锁。
但这个玩法又引入了新问题:原子性。SETNX和EXPIRE是两个独立的命令,如果客户端在SETNX成功之后、执行EXPIRE之前崩溃了,这个锁依然成了死锁,这不是一个可靠的做法。
标准款:SET命令一步到位(自动挡)
Redis 2.6.12版本之后,SET命令增强了功能,可以一步到位地实现设值和设置过期时间,并且保证原子性,命令是这样用的:SET lock_order_123 1 NX PX 10000。
NX:就是SETNX的那个NX,表示“只有当键不存在时才设置”。PX 10000:设置过期时间,单位是毫秒,这里表示10秒。
这是目前实现Redis锁的基础标准姿势,解决了原子性问题,避免了设置过期时间前的崩溃风险,但光有这个还不够,还有一个致命问题。
防误删款:SET NX PX + 唯一值验证
想象这个场景:客户端A拿到锁(过期时间10秒),但它的业务逻辑执行了太久,花了15秒,在第10秒时,锁因为过期自动释放了,此时客户端B成功拿到了锁,刚过5秒(总第15秒),客户端A傻乎乎地执行完了,然后它去执行DEL命令,结果把客户端B刚创建的锁给删了!客户端C趁机也拿到了锁,这下就可能出现多个客户端同时操作共享资源的混乱局面。
解决方案是:在设置锁的值时,不要简单地设为1,而是设为一个全局唯一的值,比如UUID或者请求ID,释放锁的时候,先获取一下当前锁的值,判断是不是自己设置的那个唯一值,如果是,才能删除。
“获取值”和“删除”又是两个命令,需要保证原子性,否则判断完是自己的值之后,锁可能又过期被别人抢走了,这里就需要用到Lua脚本了,因为Lua脚本在Redis中是原子执行的,所以完整的流程是:
- 加锁:
SET lock_order_123 [唯一随机值] NX PX 10000 - 解锁:执行一段Lua脚本:
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end这段脚本能确保“检查”和“删除”这两个动作是连续的、原子的。
高可用款:Redlock算法
上面所有的讨论都基于一个前提:我们只使用一个Redis实例,但如果这个Redis实例宕机了,整个锁服务就不可用了,为了解决单点故障问题,Redis的作者Antirez提出了一个叫做Redlock的算法。
Redlock的思路是,你不用一个Redis实例,而是用5个(建议是奇数个)独立的Redis主节点(注意是主节点,不是从节点,且它们之间没有复制关系),客户端想要获取锁时,需要依次向这5个实例发送加锁命令,只有当超过半数的节点(比如3个)都成功设置锁,并且总耗时小于锁的过期时间,才算加锁成功,释放锁时,也需要向所有节点发送释放命令。
Redlock的目的是通过部署多个独立的Redis实例,降低整个锁服务完全不可用的概率,但它非常重,实现复杂,而且一直存在争议(比如Martin Kleppmann就曾发文质疑其安全性),除非是在对一致性要求极高、且能接受其复杂性和性能损耗的场景,一般不太常用,更多时候,人们会通过Redis哨兵(Sentinel)或集群(Cluster)模式下的主从切换来保证锁服务的可用性,但这会引入主从数据延迟带来的新的复杂性。
总结一下玩法进阶路线:
从最危险的SETNX/DEL手动挡,到有死锁风险的SETNX+EXPIRE半自动,再到原子性的SET NX PX标准自动挡,最后到通过唯一值和Lua脚本解决误删问题,这是单机Redis锁的成熟形态,如果还要追求更高可用性,才会去考虑像Redlock这样的分布式算法,在实际项目中,95%以上的场景,用“标准款”加上“防误删款”的组合就已经非常稳健了。

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