Redis里头那个分布式锁是咋整的,锁设计到底讲了啥意思
- 问答
- 2025-12-25 12:28:00
- 2
你想知道Redis里头那个分布式锁是咋回事,锁设计讲的到底是啥意思,说白了,这玩意儿就是为了解决一个很简单但又很头疼的问题:当你的系统有好几个服务(比如两台电脑或者两个程序)同时想修改同一个东西的时候(比如电商里扣减同一件商品的库存),怎么保证它们不会乱来,不会把库存扣成负数。
想象一下,你和另一个人同时想进一个只有一个座位的电话亭打电话,要是两个人一起挤进去,那肯定就乱套了,这时候,你们需要一个办法来协调谁先谁后,这个办法就是“锁”,一个人进去后,从里面把门锁上,外面的人看到门锁了,就知道得等着,里面的人打完电话出来,把锁打开,外面的人才能进去,Redis分布式锁干的就是这个“管理电话亭门锁”的活儿。
最基础的做法是咋整的?
根据Redis官方文档(SET命令的文档)里提到的用法,最核心、最原始的想法就是用Redis里的一个key来代表一把锁,那个电话亭的锁,我们就用key叫telephone_booth_lock。
-
加锁:你想进电话亭,就执行一个Redis命令:
SET telephone_booth_lock my_unique_value NX PX 30000。NX的意思是,只有当这个key不存在的时候,我才能设置成功,这就好比你去拧门锁,如果门是开的(锁不存在),你才能把它锁上,如果别人已经锁上了(key已经存在),你这个命令就失败,加锁不成功,那就得等着。PX 30000是给这把锁设置一个30秒的过期时间,这非常非常关键!为啥呢?万一那个拿到锁的人(服务)在里面出事了,比如突然死机了,或者网络断了,它可能永远都不会主动出来开门(释放锁),如果没有过期时间,这个锁就永远被占着,后面所有人都别想用了,这就是“死锁”,设了过期时间,就算拿锁的人挂了,30秒后锁自动失效,别人也能进去,这相当于给电话亭装了个定时爆破装置,超过时间自动把门炸开。my_unique_value这个值也很重要,它必须是唯一标识你这个请求的,比如用UUID,为啥不能随便写个值呢?这是为了防止误删别人的锁,比如你设的锁值是client1,你操作比较慢,锁过期自动释放了,此时另一个人(client2)成功加锁,这时如果你的程序终于反应过来,去执行释放锁的命令(删除key),就会把client2的锁给删了!释放锁的时候,你要先检查一下这个key对应的值是不是你当初设置的那个my_unique_value,如果是,才能删,这好比你在锁门上贴了个只有你自己知道的暗号,开门前先对暗号,对上了才开。
-
释放锁:你打完电话出来了,就需要释放锁,这个过程得用Lua脚本来保证原子性(原子性就是说几个操作要么一起成功,要么一起失败,中间不会被打断),脚本大概逻辑是:
- 先获取
telephone_booth_lock这个key的值。 - 如果取到的值等于
my_unique_value(对暗号),那我就执行DEL命令把这个key删掉,锁就释放了。 - 如果值不匹配,说明锁已经不是我的了,那我就什么都不做。
- 先获取
光有基础做法够不够?锁设计还讲了啥?
上面说的只是最基础的模型,但在真实世界里,情况更复杂,所以Redis的开发者Antirez(Salvatore Sanfilippo)在他的一篇经典博客《How to do distributed locking》里,深入讲了设计一个靠谱的分布式锁需要考虑啥,他主要提了几个关键点,我用人话给你讲讲:
-
安全属性(Safety Property):这是底线,意思是互斥,在任何时候,只能有一个客户端能持有锁,这是最基本的要求,我们上面用
NX就是为了保证这个。 -
活性属性(Liveness Property):锁不能成了系统死结,得能正常工作,这又分两点:
- 无死锁:最终总得有个客户端能拿到锁,不能大家都卡死,我们上面说的“过期时间”就是解决死锁的。
- 容错性:Redis节点万一挂了一部分,锁服务还得能一定程度正常工作,这引出了下一个大问题——Redlock算法。
-
Redlock算法(解决单点故障问题)
- 问题在哪:我们刚才说的都是在只有一个Redis主节点的情况下,如果这个唯一的Redis节点宕机了,那整个锁服务就全挂了,即使Redis有主从复制,也不行,因为复制是异步的,你在主节点上成功加锁,但这个写命令还没同步到从节点,主节点就宕机了,从节点被提升为新主,但新主上根本没有刚才那把锁的记录!这时另一个客户端来加锁,也能成功,这样一来,就有两个客户端同时认为自己持有锁,互斥性就被破坏了。
- Redlock咋整:它的核心思想是,别只依赖一个Redis实例,用多个(比如5个)独立的Redis主节点(不是主从关系,而是完全互相独立的实例,防止同时宕机),加锁的过程是:
- 客户端记录当前时间。
- 依次向这5个实例发送加锁命令(和单节点一样,用
NX和PX)。 - 只有当客户端从大多数(N/2 + 1,比如5个中的3个) 的节点上都成功获取到锁,并且总的加锁耗时小于锁的过期时间,才算加锁成功。
- 如果加锁失败(比如没拿到大多数),那就立即向所有节点发送释放锁的命令(即使那个节点你没加成功也要发,清理现场)。
- 为啥这样能行:因为它不依赖单个节点的可靠性,只要大多数节点还活着,并且客户端在锁的有效期内完成了操作,就能保证锁的安全性,即使少数节点挂了或者延迟很高,也没关系,这有点像投票,多数票通过才算数。
总结一下锁设计讲的中心思想
Redis分布式锁的设计,从头到尾都在纠结和平衡三件事:
- 怎么保证唯一性(互斥):用
NX命令,抢不到就排队。 - 怎么防止死锁:给锁加个过期时间,这是保底的救命措施。
- 怎么防止意外删了别人的锁:给锁加一个唯一标识(那个
my_unique_value),释放的时候要验明正身。 - 怎么应对Redis本身挂掉:用Redlock这种多节点方案,用空间(多台机器)换可靠性,不再把鸡蛋放在一个篮子里。
最后要提一句,Redlock算法在业界是有争议的(比如Martin Kleppmann有篇著名的文章反驳它),它并不能100%保证安全,但在很多要求不是极端苛刻的场景下,它已经是一个实践性很强的方案了,说到底,分布式系统里没有银弹,每一种选择都是在权衡利弊。

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