分布式锁这东西真复杂,反复琢磨后我总结了几个靠谱方案分享给你
- 问答
- 2026-01-11 16:43:02
- 2
笔者结合过往项目经验与技术社区常见讨论总结)
分布式锁这东西,真不是一般的复杂,刚开始接触的时候,觉得不就是一把锁嘛,单机程序里用得好好的,到了分布式系统里,无非是找个大家都能访问的地方存一下锁的状态,可真动手实现或者选型的时候,各种问题就冒出来了:锁怎么算加上了?客户端挂了锁怎么释放?网络延迟了会不会导致两个客户端同时拿到锁?锁的过期时间设多久合适?这些问题就像打地鼠一样,解决一个,另一个又冒出来。
经过反复折腾和琢磨,我算是明白了,没有一种分布式锁方案是完美的,银弹不存在,不同的方案其实是在一致性、可用性、性能、复杂度之间做权衡,下面我就分享几个我觉得在大多数场景下比较靠谱的方案,以及它们各自的“坑”在哪里,帮你少走点弯路。
第一个方案,也是现在最流行的:基于Redis的分布式锁。
这大概是大家最先想到和用到的方案,思路很直观:Redis速度快,单命令是原子的,正好可以用来存一个锁的键值对,一个业务锁的key是"order_lock_123",value用一个唯一的客户端标识(比如UUID),然后执行SET key value NX PX 30000,这个命令的意思是,只有当这个key不存在(NX)时才能设置成功,并且给这个key设置30秒的过期时间(PX),如果设置成功,就算加锁成功;否则就加锁失败。
这套方案的核心要点有几个:
- 必须设置过期时间:这是为了防止客户端加锁后崩溃,导致锁永远无法释放,变成“死锁”,过期时间是个权衡,设短了可能业务没执行完锁就丢了,设长了其他客户端要等很久。
- value必须全局唯一:这个值用来标识加锁的客户端,释放锁的时候,要用Lua脚本先判断当前锁的value是不是自己设置的,然后再删除,这是因为要避免误删别人的锁,想象一下:客户端A加锁成功,但执行时间超过了锁的过期时间,锁自动释放了,此时客户端B加锁成功,然后客户端A执行完了,直接去释放锁,就把B的锁给删了,用Lua脚本保证“判断+删除”的原子性就能解决这个问题。
Redis锁的优点很明显:性能极高,实现起来相对简单,资料多,但它有个硬伤:它通常是基于主从异步复制的,如果主节点加锁成功后,在数据同步到从节点之前,主节点宕机了,从节点被选为新主,但这个锁的状态丢失了,其他客户端就可能再次加锁成功,导致问题,虽然Redis官方后来提出了Redlock算法来尝试解决这个问题,但Redlock本身也引发了很大的争议(比如Redis之父Antirez和分布式系统专家Martin Kleppmann之间就有过著名的辩论),它更复杂,而且并不能保证绝对的安全,对于绝大多数要求不是极端严格的场景,单Redis节点+正确设置过期时间和唯一value的方案就够用了;如果要求高,可能需要考虑下面更重的方案。
第二个方案,基于ZooKeeper的分布式锁。
Zookeeper是专门为分布式协调而设计的,用它实现分布式锁更“原生”、更严谨,常见的做法是利用它的“临时顺序节点”。
流程大概是这样的:
- 每个客户端都在一个指定的锁节点(比如
/locks/my_lock)下,创建自己的临时顺序节点。 - 客户端获取
/locks/my_lock下所有的子节点。 - 判断自己创建的子节点是不是序号最小的那个,如果是,说明自己拿到了锁。
- 如果不是,就对自己序号的前一个节点设置监听(watch),当前一个节点被删除时(意味着前一个客户端释放了锁),ZooKeeper会通知当前客户端,它就可以再次尝试获取锁。
这个方案的优势非常强:
- 锁是安全的:因为ZK保证了强一致性,锁的状态在集群内是可靠的,不会出现Redis那种因主从切换导致锁丢失的问题。
- 自动释放:临时节点的特性是,当客户端与ZK服务器的会话失效时(比如客户端宕机),节点会自动被删除,这就天然避免了死锁,不需要设置过期时间。
- 公平锁:通过顺序节点和监听机制,客户端获取锁的顺序就是它们请求的顺序,实现了公平排队,不会出现“饿死”现象。
但缺点也同样明显:性能比Redis差很多,因为每次加锁释放锁都要创建、删除节点,还可能涉及网络通信和监听通知,你需要额外维护一个ZK集群,增加了系统复杂度,ZK锁更适合对锁的可靠性要求极高,但并发量不是天量,且已经存在ZK集群的场景。
第三个方案,基于数据库(如MySQL)的分布式锁。
这算是一个比较“朴素”和“保守”的方案,在一张表里建一个唯一索引的字段作为锁标识,加锁就是执行一条插入语句(INSERT INTO lock_table (lock_name) VALUES ('order_lock')),利用数据库的唯一约束,插入成功就算加锁成功,插入失败(因为唯一冲突)就算加锁失败,释放锁就是删除这条记录。
这个方案的优点是:理解起来最简单,如果系统里已经有数据库,几乎不需要引入新的中间件,但缺点一大堆:数据库性能是瓶颈,频繁加锁释放锁对数据库压力大;而且没有自动过期机制,如果客户端崩溃,需要手动清理,或者得靠定时任务扫描,非常麻烦,还有一种基于数据库乐观锁(用版本号)的方式,但那通常用于解决更新冲突,不太像传统的互斥锁,数据库锁一般只在非常简单的场景,或者并发量极低,并且没有其他选择时才考虑。
怎么选?看你的实际需求。
- 要性能,能接受极小概率的锁失效:用Redis锁,并做好异常处理(比如锁失效后要有补偿机制)。
- 要绝对可靠,宁可牺牲一些性能:用ZooKeeper锁。
- 系统简单,没啥并发,图省事:或许可以用数据库锁,但真心不推荐作为首选。
最后还得啰嗦一句,分布式锁是解决并发问题的有力工具,但也是个“重器”,在设计系统时,可以多想想是否真的需要一把“全局锁”?能不能用队列串行化处理?或者通过设计将资源分区,避免竞争?很多时候,这些方法可能比直接上分布式锁更优雅、更高效,希望我琢磨的这点经验对你有帮助。

本文由帖慧艳于2026-01-11发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://www.haoid.cn/wenda/78796.html
