一文搞懂各种分布式锁的原理
分布式锁是控制分布式系统中不同进程/节点对共享资源进行互斥访问的核心手段,其核心目标是保证在分布式环境下,同一时刻只有一个节点能获取到锁并执行临界区代码,避免数据不一致、并发冲突等问题。
本文将从分布式锁的核心要求出发,逐一拆解Redis、ZooKeeper、Etcd、数据库等主流分布式锁的实现原理、优缺点及适用场景,帮你全面掌握其核心逻辑。
一、分布式锁的核心要求
要实现一个可靠的分布式锁,必须满足以下4个核心条件,缺一不可:
- 互斥性:同一时刻,只有一个客户端能持有锁。
- 防死锁:锁必须有超时自动释放机制,避免客户端宕机后锁永久持有。
- 可重入性(可选):同一客户端获取锁后,可重复获取同一把锁,避免死锁。
- 高可用/高性能:锁的获取/释放过程要高效,且能应对节点宕机等故障。
此外,还需满足原子性(获取/释放锁的操作是原子的)、容错性(集群环境下不丢锁)等附加要求。
二、基于Redis的分布式锁
Redis是分布式锁最常用的实现方案,基于其单线程执行和丰富的数据结构,实现简单且性能高。
1. 基础版Redis分布式锁(SETNX + EXPIRE)
实现原理
利用Redis的SETNX(SET if Not eXists,不存在则设置)命令实现锁的获取,EXPIRE设置锁超时时间防死锁:
- 获取锁:执行
SETNX lock_key client_id,若返回1则获取锁成功;返回0则锁已被持有,获取失败。 - 释放锁:执行
DEL lock_key,删除锁键即释放。 - 防死锁:通过
EXPIRE lock_key 10设置锁超时(如10秒),即使客户端宕机,锁也会自动释放。
核心缺陷
- 原子性问题:
SETNX和EXPIRE是两个独立命令,若执行完SETNX后客户端宕机,EXPIRE未执行,锁会永久持有。 - 锁误释放:客户端A获取锁后超时,锁自动释放;客户端B获取锁,此时A恢复并执行
DEL删除B的锁,导致锁被误删。 - 不可重入:同一客户端重复执行
SETNX会返回0,无法获取锁。
2. 进阶版Redis分布式锁(SET命令原子化)
实现原理
Redis 2.6.12+版本支持SET命令的扩展参数,可将获取锁+设置超时合并为一个原子操作,解决基础版的原子性问题:
- 获取锁:执行
SET lock_key client_id EX 10 NX,其中:EX 10:设置超时时间10秒;NX:仅当键不存在时设置,等价于SETNX。
- 释放锁:需先判断锁的持有者(
client_id)是否为当前客户端,再执行DEL,避免误释放。- 示例:
GET lock_key获取值,若等于当前client_id则DEL。
- 示例:
解决的问题
- 解决了
SETNX+EXPIRE的原子性问题,避免锁永久持有。 - 增加
client_id校验,避免误释放其他客户端的锁。
3. 高级版Redis分布式锁(Redlock算法)
实现原理
针对Redis单节点故障导致的锁丢失问题,Redis官方提出Redlock算法,基于Redis集群(多主节点)实现高可用分布式锁:
- 客户端获取当前时间戳(毫秒)。
- 依次向N个独立的Redis主节点(通常N≥5)执行获取锁操作(同进阶版
SET lock_key client_id EX 10 NX),每个节点设置超时时间(远小于锁总超时,如50ms)。 - 客户端统计成功获取锁的节点数,若成功数≥N/2+1且总耗时<锁超时时间,则认为获取锁成功;否则释放所有节点的锁。
- 释放锁:向所有Redis节点执行释放锁操作(删除键)。
核心优势
- 解决单节点Redis宕机导致的锁丢失问题,高可用更强。
缺陷
- 性能损耗:需操作多个Redis节点,开销高于单节点锁。
- 复杂度高:算法实现繁琐,需处理节点宕机、时钟偏移等问题。
Redis分布式锁总结
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 基础版(SETNX+EXPIRE) | 实现简单 | 原子性差、易误释放、不可重入 | 低并发、简单场景 |
| 进阶版(SET EX NX) | 原子性好、防误释放 | 单节点故障丢锁、不可重入 | 高并发、单Redis节点场景 |
| Redlock算法 | 高可用、防丢锁 | 性能低、实现复杂 | 强一致性、高可靠场景 |
三、基于ZooKeeper的分布式锁
ZooKeeper是分布式协调框架,基于临时有序节点和Watcher监听机制实现分布式锁,天然支持高可用和防死锁。
1. 实现原理
ZooKeeper的数据模型是树形节点(ZNode),核心利用两类节点特性:
- 临时节点(EPHEMERAL):客户端宕机后,临时节点会自动删除,避免死锁。
- 有序节点(SEQUENTIAL):创建节点时自动分配递增序号,保证顺序。
独占锁(互斥锁)实现步骤
- 客户端在锁的根节点(如
/lock)下创建临时有序节点,如/lock/lock-000001。 - 客户端获取
/lock下的所有子节点,判断自己创建的节点是否为序号最小的节点:- 若是:获取锁成功,执行临界区代码。
- 若否:监听比自己序号小1的节点(如创建
lock-000002则监听lock-000001)的删除事件。
- 当监听到前一个节点删除时,重新判断自己是否为最小节点,若是则获取锁成功。
- 释放锁:客户端执行完临界区代码后,主动删除自己创建的临时节点。
可重入锁实现
在客户端本地记录锁的持有次数,获取锁时次数+1,释放时次数-1,次数为0时才删除节点。
2. 核心优势
- 高可用:ZooKeeper集群部署,节点宕机不影响锁服务。
- 防死锁:临时节点自动释放,避免锁永久持有。
- 公平性:基于有序节点,先请求先获取锁,保证公平性。
3. 缺陷
- 性能低于Redis:每次锁竞争需创建/删除节点,且Watcher监听有开销。
- 实现复杂度高:需处理节点监听、异常恢复等逻辑。
ZooKeeper分布式锁总结
| 优点 | 缺点 | 适用场景 |
|---|---|---|
| 高可用、公平性、防死锁 | 性能一般、实现复杂 | 强一致性、低并发高可靠场景 |
四、基于Etcd的分布式锁
Etcd是云原生场景下的分布式键值存储,基于Raft协议保证一致性,实现分布式锁的逻辑与ZooKeeper类似,但更轻量。
1. 实现原理
Etcd的核心特性:临时租约(Lease)、原子化CAS(Compare-And-Swap)、前缀监听。
独占锁实现步骤
- 客户端创建一个临时租约(设置超时时间,防死锁),并将租约与锁键(如
/lock/resource)绑定。 - 执行原子化CAS操作:尝试将锁键的值设置为当前客户端ID,仅当键不存在时设置成功(获取锁)。
- 若CAS失败(锁已被持有),客户端监听锁键的删除事件,等待锁释放后重新竞争。
- 释放锁:客户端主动删除锁键,或租约到期后锁键自动删除。
2. 核心优势
- 基于Raft协议,强一致性,锁可靠性高。
- 轻量级,部署简单,适合云原生场景。
3. 缺陷
- 性能低于Redis,高于ZooKeeper。
- 生态适配性不如Redis广泛。
Etcd分布式锁总结
| 优点 | 缺点 | 适用场景 |
|---|---|---|
| 强一致、轻量、防死锁 | 性能一般、生态有限 | 云原生、强一致分布式场景 |
五、基于数据库的分布式锁
基于数据库的分布式锁是最原始的实现方案,利用数据库的行锁/表锁或唯一索引实现互斥。
1. 基于数据库表的悲观锁
实现原理
创建一张锁表,包含lock_key(锁名)、owner(持有者)、expire_time(过期时间)等字段:
- 获取锁:执行
INSERT INTO lock (lock_key, owner, expire_time) VALUES ('resource1', 'client1', NOW()+10),利用数据库唯一索引的互斥性,插入成功则获取锁,失败则锁被持有。 - 释放锁:执行
DELETE FROM lock WHERE lock_key='resource1' AND owner='client1'。 - 防死锁:通过
expire_time定期清理过期锁。
2. 基于数据库表的乐观锁
实现原理
在业务表中增加version版本字段,通过CAS更新实现锁:
- 获取锁:无显式加锁,执行更新时判断版本:
UPDATE table SET version=version+1 WHERE id=1 AND version=1,更新成功则获取锁(执行临界区),失败则重试。 - 释放锁:无显式释放,更新完成即释放。
数据库分布式锁总结
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 悲观锁 | 实现简单 | 性能差、依赖数据库、易数据库单点故障 | 低并发、数据库已存在的场景 |
| 乐观锁 | 无锁竞争开销 | 仅适用于更新场景、需处理版本冲突 | 读多写少、低并发场景 |
六、各类分布式锁对比总结
| 锁方案 | 性能 | 可靠性 | 实现复杂度 | 公平性 | 适用场景 |
|---|---|---|---|---|---|
| Redis(单节点) | 极高 | 单节点故障丢锁 | 低 | 非公平 | 高并发、简单互斥场景 |
| Redis(Redlock) | 高 | 高可用 | 中 | 非公平 | 强一致、高可靠分布式场景 |
| ZooKeeper | 中 | 高可用 | 中高 | 公平 | 强一致、低并发高可靠场景 |
| Etcd | 中高 | 强一致 | 中 | 非公平 | 云原生、强一致分布式场景 |
| 数据库 | 低 | 依赖数据库 | 低 | 非公平 | 低并发、数据库已存在的场景 |
七、选型建议
- 高并发、简单互斥:选Redis单节点锁(性能最优)。
- 强一致、高可靠:选Redlock(Redis集群)、ZooKeeper或Etcd。
- 云原生场景:选Etcd。
- 低并发、依赖数据库:选数据库锁。
- 需要公平锁:选ZooKeeper。
核心原则:优先选Redis(性能),其次选ZooKeeper/Etcd(可靠),最后选数据库(依赖现有资源)。

浙公网安备 33010602011771号