在现代分布式系统中,“锁”已经不再是简单的多线程互斥,而是跨机器、跨节点、跨服务的全局协调机制。“锁”用不好,轻则业务重复执行,重则库存超卖、资金错乱、系统雪崩。
Redis、etcd、Zookeeper和数据库DB 作为实现分布式锁的常用中间件,它们的使用方式、技术原理有哪些区别,以及各自的最佳使用场景是什么呢,让我们一起揭晓吧!
01
为什么需要分布式锁?
在工程实践中,我们经常会遇到各种场景要求对共享资源进行互斥操作,否则整个系统的数据一致性就会出现问题。典型场景如商品库存操作、Kubernertes 调度器为 Pod 分配运行的 Node。
要实现对共享资源的互斥操作,“锁”就是一个通用的解决方案。
在单机应用中,多个线程竞争同一个资源,我们使用本地的互斥锁就可以完成资源的互斥操作。
然而单节点存在单点故障,为了保证服务高可用,就需要多节点部署。在多节点部署的分布式架构中,就要求我们使用分布式锁来解决资源的互斥操作。
分布式锁,就是能跨进程、跨节点、跨服务都有效的锁。
02
分布式锁要解决哪些问题?
一把好的“分布式锁”,应该要具备以下能力:
能力 | 能力内容 |
互斥性 | 同一时间只有一个客户端拿到锁 |
避免死锁 | 锁必须有超时机制,防止持锁服务异常挂掉 |
高可用 | 即使节点宕机,也不能影响锁一致性 |
性能 | 不能影响系统整体吞吐量 |
安全释放 | 防止“误删别人的锁”这种灾难 |
03
分布式锁的“四大天王”
分布式锁的实现本质是依赖一个所有客户端都能访问的第三方共享存储系统,利用该系统的互斥机制来充当“锁”的角色。
1、Redis分布式锁:
性能王者
Redis 是分布式锁使用最广泛的方案,原因很简单:快,并且部署维护相对简单。
实现方式
核心原理是利用 Redis 的原子性 SET 命令,并配合过期时间,实现加锁和防死锁。
(1)Redis 加锁
SET lock_key unique_id NX EX 30- lock_key:锁的键名。
- unique_id:唯一随机值,用于标识请求客户端,确保只有加锁者才能解锁(防止误解锁)。
- NX:Not Exist,只有当 lock_key 不存在时才能设置成功,保证互斥性。
- EX 30:设置 30 秒过期时间,防止客户端宕机导致死锁(过期性)。
(2)Redis 解锁
必须使用 Lua 脚本保证解锁操作的原子性:先判断 unique_id 是否匹配,再执行 DEL。
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1])else return 0end优点
- 性能极高:基于内存,加解锁速度快。(这也是广泛使用Redis的原因之一)
- 实现简单:使用 Redisson 等框架非常方便。
- TTL 自动避免死锁。
缺点
- 可靠性稍差:在主从复制模式下,主节点宕机且锁未同步到从节点时,可能导致新主节点被重复加锁(锁丢失)。
- 需要解决锁续期:需要 Watch Dog 机制防止长时间业务执行时锁过期。
我们知道 Redis 是基于主备异步复制协议实现的 Master-Slave 数据同步,主备切换可能导致分布式锁出现的安全性问题(master未实时将锁同步到slave),是Redis 实现的分布式锁,相比后边的etcd和zk的最大问题。
2、etcd 分布式锁:
云原生新贵
etcd 是专为云原生应用设计的高可用键值存储系统,基于 Raft 协议,是 Kubernetes 的核心组件。
实现方式
etcd 实现分布式锁与 ZK 类似,但利用的是其 Lease(租约)机制 和 Revision(版本号)。
(1)etcd 加锁
- 客户端向 etcd 申请一个租约(Lease),该租约有一个 TTL(Time To Live,生存时间)。
- 客户端尝试使用 Txn(事务)操作,在 etcd 中创建一个 key(作为锁),并将该 key 绑定到租约上。
- 创建时,可以利用 If 条件保证 key 不存在时才创建成功(互斥性)。
我们可以通过 key 的创建版本号 create_revision 来检查 key 是否已存在,因为一个 key 不存在的话,它的 create_revision 版本号就是 0。
示例代码如下:
txn := client.Txn(ctx).If(v3.Compare(v3.CreateRevision(k), "=", 0))txn = txn.Then(v3.OpPut(k, val, v3.WithLease(s.Lease())))txn = txn.Else(v3.OpGet(k))resp, err := txn.Commit()if err != nil { return err}(2)etcd 解锁
- 获取锁失败的客户端,可以通过 Watch 机制监听该 key 的变化。
- 客户端通过删除 key 来释放锁。
- 一旦租约到期,即使客户端宕机,etcd 也会自动删除该 key,实现防死锁。
- 当其他 client 收到 Watch Delete 事件后,就可快速判断自己是否有资格获得锁,极大减少了锁的不可用时间。
etcd 解锁代码如下:
var wr v3.WatchResponsewch := client.Watch(cctx, key, v3.WithRev(rev))for wr = range wch { for _, ev := range wr.Events { if ev.Type == mvccpb.DELETE { return nil } }}优点
- 强一致性:基于 Raft 协议,可靠性高。
- 云原生友好:天然与 K8s 集成,适合容器化环境。
- Lease 机制:内置租约,实现锁续期和防死锁更简洁。
etcd 实现的分布式锁,安全性极高,不会像Redis可能出现多个client获得锁的情况。
缺点
- 生态相对较新:分布式锁的客户端和框架成熟度不如 ZK 和 Redis。
- 性能中等:比 Redis 慢,比 ZK 快一些,但总体性能取决于集群负载。
3、ZK 分布式锁:
一致性之王
ZooKeeper 基于 ZAB 协议,天生具备强一致性和高可靠性,是实现分布式协调服务的传统选择。
实现方式
核心原理是利用 ZK 的临时有序节点和 Watcher 监听机制。

(1)ZooKeeper 加锁
- 所有客户端在 ZK 的一个持久化父节点(如 /locks)下,创建临时有序子节点。例如:客户端 A 创建 /locks/lock-00000001,客户端 B 创建 /locks/lock-00000002。
- 客户端检查自己创建的节点是否是所有子节点中序号最小的。
- 如果是最小节点,则成功获取锁。
(2)ZooKeeper 解锁
- 如果不是最小节点(例如 B),则监听比自己序号小 1 的那个节点(例如 B 监听 A 的节点 lock-00000001)。
- 持有锁的客户端完成业务后,删除自己创建的节点。
- ZK 通过 Watcher 机制通知下一个被监听的客户端(B)。
- 被唤醒的客户端(B)重新检查子节点列表,发现自己已是最小节点,则获取锁。
优点
- 高可靠性/强一致性:基于 ZAB 协议,锁的获取和释放状态不会丢失。
- 公平锁:基于有序节点机制,天然支持公平锁,先到先得。
- 防死锁:利用临时节点特性,客户端断开连接,锁自动释放。
缺点
- 性能相对较差:加解锁涉及 ZK 集群的写入操作、Watch 机制,性能低于 Redis。
- 系统复杂度高:需要独立部署和维护 ZooKeeper 集群。
4、数据库分布式锁:
最简单但性能最弱
实现方式
利用数据库的唯一索引或事务隔离性实现互斥。这是最原始、最容易理解的方案。
(1)方案一:唯一索引(排它锁)
- 创建一个锁表,包含 resource(资源名称,唯一索引)和 owner(持有者)。
- 加锁:客户端尝试向表中插入一条记录:
INSERT INTO lock_table (resource, owner, expire_time) VALUES ('product_A_stock', 'client_id_A', NOW() + INTERVAL 30 SECOND);- 由于 resource 字段有唯一索引,只有一个客户端能插入成功,即成功获取锁。
- 解锁:客户端删除该记录。
(2)方案二:行级锁(SELECT FOR UPDATE)
在事务中,客户端通过 SELECT FOR UPDATE 语句尝试锁定某一行记录。
-- 事务开始BEGIN;SELECT * FROM lock_table WHERE resource = 'product_A_stock' FOR UPDATE;-- 如果能执行到这里,说明成功获取锁-- ... 执行业务逻辑-- 事务结束,释放锁COMMIT;优点
- 简单易懂:利用数据库的成熟特性,无需引入新组件。
- 可靠性高:依赖数据库的事务和持久化机制。
缺点
- 性能最差:数据库是共享资源的瓶颈,在高并发下性能急剧下降。
- 死锁风险:若客户端宕机,锁记录可能永远存在(方案一),或事务未提交导致锁不释放(需要外部定时任务清理)。
- 锁过期难处理:需要额外的定时任务扫描和清理过期锁记录。
04
四大锁方案对比与选型指南
锁方案 | 性能 (并发能力) | 可靠性 | 实现复杂度 | 推荐场景 |
Redis | 极高 | 较高 | 低 (使用框架) | 绝大多数高并发业务场景 |
etcd | 中高 | 极高 (强一致) | 中 (云原生环境) | 云原生系统或Kubernetes 生态环境下的服务 |
Zookeeper | 中低 | 极高 (强一致) | 高 (需维护 ZK 集群) | 对数据一致性要求极高的场景(如金融核心)或涉及选举、调度、大数据等 |
数据库 | 最低 | 较高 | 低 (无需新组件) | 并发量极低或无需引入新中间件的简单场景 |
(1)关心性能,选 Redis
典型如库存、订单、幂等控制
(2)注重强一致性,选etcd或Zookeeper
比如调度系统、Leader选举
(3)如果不想引入新组件,选数据库
适合小业务、低并发的简单场景
分布式锁不是只有 Redis,而Zookeeper 也不是过时技术,etcd 也不是 K8s 专用。只有在明确业务需求后,根据一致性、性能、复杂度做选型,才能避免踩坑。