分布式缓存锁详解
在分布式系统中,由于多个服务节点同时运行,传统的本地锁(如 Java 的 synchronized 或 ReentrantLock)只能锁定单个 JVM 进程内的资源,为了在多个独立节点之间同步访问共享资源,需要引入分布式锁。
什么是分布式锁
分布式锁是一种在分布式环境下,确保同一时刻只有一个客户端能持有该锁并操作共享资源的机制,它通常通过一个外部的共享存储(如 Redis、Zookeeper 或 Etcd)来实现。
为什么需要分布式锁
- 防止数据重复写入:例如在秒杀场景中,防止同一个订单被多次创建。
- 保证数据一致性:当多个节点同时修改同一个数据库记录时,避免并发导致的数据覆盖。
- 避免重复执行任务:在定时任务调度中,确保一个任务在集群中只由一个节点执行。
基于 Redis 的分布式锁实现
Redis 是最常用的分布式锁实现方案,主要依赖其单线程特性和原子操作。
基础实现:SETNX
使用 SETNX (Set if Not Exists) 命令,只有当 Key 不存在时才会设置成功。
- 命令:
SET lock_key unique_value NX PX 30000 - 关键点:
-
NX
:保证互斥性。 - PX:设置过期时间,防止持有锁的节点宕机导致死锁。
- unique_value:为每个请求生成唯一标识(如 UUID),防止 A 线程误删 B 线程的锁。
-
锁释放的原子性
释放锁时需要判断锁是否由当前线程持有,然后再删除,如果分两步执行(查询 $rightarrow$ 删除),会产生竞态条件。
- 解决方案:使用 Lua 脚本,将“判断”和“删除”封装在 Lua 脚本中,Redis 会将其作为单个原子操作执行。
核心挑战与优化
- 锁过期提前失效:如果业务执行时间超过了锁的过期时间,锁会被自动释放,导致其他线程进入临界区。
- 解决方案(看门狗机制):在锁持有期间,启动一个后台定时任务,在锁即将过期时自动续期(Renew),Redisson 框架通过
Watchdog实现了这一机制。
基于 Zookeeper 的分布式锁实现
Zookeeper 通过临时顺序节点来实现分布式锁。
- 实现流程:
- 客户端在
/lock目录下创建临时顺序节点(Ephemeral Sequential)。 - 客户端获取该目录下所有子节点,如果自己创建的节点是
序号最小
的,则获得锁。 - 如果不是最小,则对前一个序号的节点设置 Watch 监听,等待其删除后再次尝试。
- 客户端在
- 优点:
- 可靠性高:基于 CP 模型,保证强一致性。
- 自动释放:客户端崩溃或连接断开,临时节点会自动删除,无需手动设置过期时间。
- 缺点:
- 性能较低:频繁的节点创建和删除会给 Zookeeper 集群带来较大压力。
Redis 与 Zookeeper 方案对比
| 特性 | Redis (Redisson) | Zookeeper |
|---|---|---|
| 性能 | 极高 (内存操作) | 较低 (磁盘/网络开销) |
| 一致性 | 最终一致性 (AP) | 强一致性 (CP) |
| 可用性 | 高 (哨兵/集群) | 依赖 Quorum 机制 |
死锁处理 | 依赖过期时间/看门狗 | 依赖临时节点自动删除 |
| 适用场景 | 高并发、对性能要求极高 | 对数据一致性要求极严苛 |
进阶方案:Redlock (红锁)
为了解决 Redis 单点故障导致锁丢失的问题,Redis 提出了 Redlock 算法:
- 原理:部署 $N$ 个独立的 Redis 实例(通常为 5 个),客户端尝试在所有实例上获取锁,只有在大多数($N/2 + 1$)实例上获取成功,才认为最终获得了锁。
- 目的:通过多节点投票机制,消除单点故障对锁可靠性的影响。
总结与最佳实践
- 简单场景:直接使用 Redis 的
SET NX PX或成熟的库如 Redisson。 - 高可靠场景:如果无法容忍任何锁丢失,优先选择 Zookeeper 或 Etcd。
- 注意事项:
- 务必设置过期时间,防止死锁。
- 释放锁时必须校验持有者身份。
- 尽量缩短锁的持有时间,提高系统吞吐量。
首发原创文章,作者:世雄 - 原生数据库架构专家,如若转载,请注明出处:https://idctop.com/article/493174.html



