# 分布式锁实现方式

# 介绍
在分布式系统中,当多个进程/服务需要同时访问同一个共享资源时,本地锁(如 Java 的 synchronized 或 ReentrantLock)就无能为力了。这时就需要分布式锁来保证全局互斥。
目前主流的分布式锁实现方式主要有三种:基于 Redis,基于 ZooKeeper / etcd,和基于 MySQL
# 基于Redis实现
Redis 核心利用了单线程执行命令的原子性,是最常用的高并发分布式锁方案
加锁:使用 SET key value NX PX 30000 命令。NX 保证只有 key 不存在时才能设置成功(加锁),PX 设置锁的过期时间为30000ms
解锁:需要使用 Lua 脚本来校验 value(确保解的是自己加的锁)并删除 key,保证释放锁的原子性
高可用方案(Redlock 红锁):为了解决主从架构下异步复制导致锁丢失的问题,Redis 官方提出了 Redlock 算法,通过向多个独立的 Redis 节点同时申请锁,过半成功才算加锁成功。不过该算法在业界有一定争议
# 基于 ZooKeeper 实现
ZooKeeper 利用了其临时顺序节点和事件监听机制来实现分布式锁
加锁:客户端在指定的锁节点下创建一个临时顺序节点,获取该节点下的所有子节点,如果自己创建的节点序号最小,则成功获取锁。如果自己不是最小的,就对自己前一个序号的节点注册 Watcher 监听。当前一个节点被删除(前序锁释放或客户端挂了)时,当前客户端收到通知并获取锁
优点: 临时节点在客户端宕机后会自动删除,天然避免死锁
# 基于 MySQL 实现
利用关系型数据库的特性,通常有两种做法:
基于唯一索引: 创建一张锁表,给“锁名称”加上唯一索引。加锁时直接 INSERT,成功即获取锁;解锁时 DELETE
基于排他锁: 利用 select ... for update。在事务中执行该语句,如果行被锁定,其他请求会阻塞等待,事务提交后锁释放
# 技术选型
| 维度 | Redis (Redisson) | ZooKeeper (Curator) | MySQL |
|---|---|---|---|
| 性能/吞吐量 | 极高(纯内存操作) | 中等(有频繁的节点创建与销毁) | 低(受限于磁盘 I/O 和连接数) |
| 可靠性/正确性 | 中等(主从异步复制可能丢锁;红锁较重) | 极高(CP 架构,强一致性,通过心跳感知自动释放) | 高(依靠数据库本身的事务和持久化) |
| 死锁风险 | 有(若未设置超时,或业务执行时间长于锁过期时间) | 无(客户端挂了,临时节点自动删除) | 有(需要手动清理超时数据或依靠事务超时) |
| 防刷/自动续期 | 支持(Redisson 的看门狗机制) | 天然支持(心跳维持临时节点) | 不支持(需要自己写逻辑实现) |
| 实现复杂度 | 较低(现代框架如 Redisson 开箱即用) | 较低(有 Curator 封装好的 InterProcessMutex) | 简单(但不易做好高可用和性能优化) |
选择哪种方案,本质上是在 AP(可用性+性能) 和 CP(强一致性) 之间做权衡
追求极致性能与高并发(AP 倾向):优先选择 Redis
场景: 秒杀、抢购、高频业务去重
理由: 绝大多数业务场景下,Redis 偶尔由于主从切换导致的“极小概率锁丢失”是可以容忍的(或者可以通过业务层幂等来兜底)
追求数据绝对安全、强一致性(CP 倾向):优先选择 ZooKeeper / etcd
场景: 金融资源分配、关键配置修改、对“两方同时拿到锁”零容忍的场景。
理由: ZK 的强一致性选主和临时节点机制,保证了锁绝对不会因为节点宕机而出现多主并存
轻量级、低并发、不想引入新组件:选择 MySQL
场景: 后台低频操作
理由: 如果你的系统本来就没多大并发,引入 Redis 或 ZK 反而增加了运维成本