Redis - 如何使用 Redis 实现分布式锁
文章目录分布式锁的基本要求单机版SET NX PX 一行搞定释放锁要用 Lua锁过期的另一个问题业务超时集群版的 RedLock 算法RedLock 的争议Redisson开箱即用的方案常见坑点1. 锁粒度太大2. 锁粒度太小3. 忘了 unique_value4. 没有重试机制5. 用普通 SET 代替 SETNX实践建议在分布式系统里多个进程需要协调访问共享资源时分布式锁几乎是绕不开的工具。Redis 因为性能好、部署简单、命令丰富成为实现分布式锁的最常见选择。但简单不等于容易——一把看起来能用的 Redis 分布式锁真正用到生产环境常常因为细节问题踩坑。分布式锁的基本要求任何一把分布式锁都必须满足三个性质互斥性同一时刻只有一个客户端能持有锁。死锁避免持有锁的客户端崩溃锁要能自动释放。解铃还须系铃人客户端只能释放自己持有的锁不能误删别人的。Redis 实现分布式锁的难点就在这三点上。单机版SET NX PX 一行搞定最基础的实现SET lock_key unique_value NX PX30000三个关键参数NXkey 不存在才设置保证互斥。PX 3000030 秒后自动过期防止死锁。unique_value客户端唯一标识释放时校验。加锁成功返回OK失败返回nil。释放锁要用 Lua释放锁不是简单的DEL。考虑这个场景客户端 A 加锁PX30s 客户端 A 业务执行 35sGC 卡顿等原因 锁过期客户端 B 加锁成功 客户端 A 完成业务DEL lock_key ← 删的是 B 的锁要避免这个问题释放时必须先校验持有者。但GET DEL是两条命令中间可能被打断。所以释放必须用 Lua 脚本保证原子性ifredis.call(GET,KEYS[1])ARGV[1]thenreturnredis.call(DEL,KEYS[1])elsereturn0end锁过期的另一个问题业务超时PX 30s 是个两难的选择太短业务还没做完就过期锁失效。太长进程崩溃后锁很久才能释放影响其他客户端。成熟的方案是引入看门狗watchdog机制客户端启一个后台线程定期延长锁的过期时间。Redisson 就是这么实现的——默认每 10 秒续期一次把锁的过期时间重置回 30 秒。这样只要持有者还活着锁就不会过期一旦持有者挂了看门狗也停了锁会自然过期。集群版的 RedLock 算法单机 Redis 实现的锁有个根本性问题主库挂了。如果加锁后主库还没把锁同步到从库就崩溃哨兵切换到从库新主库上根本没这个锁互斥性就被打破了。Redis 作者 Antirez 提出了 RedLock 算法思路是部署 N 个独立的 Redis 实例推荐 5 个互不主从。客户端依次向所有实例申请同一个锁记录开始时间。如果超过 N/21 个实例加锁成功且总耗时小于锁的过期时间则认为加锁成功。如果失败向所有实例发送释放请求不管之前加锁是否成功。只要多数派实例存活且没被网络隔离锁就能保持互斥性。RedLock 的争议分布式系统专家 Martin Kleppmann 写过一篇著名文章质疑 RedLock在 GC 暂停、时钟漂移等场景下RedLock 仍然不能保证安全性。Antirez 也回应过但这个争论至今没有定论。实际工程上的建议普通业务单机 Redis 短 TTL 看门狗如 Redisson 默认实现已经足够简单可靠。金融级强一致直接用 ZooKeeper 或 etcd它们的设计就是为分布式协调而生。真要用 RedLock考虑清楚是不是真的需要这种复杂度。Redisson开箱即用的方案自己实现分布式锁很容易踩坑。Java 生态里 Redisson 是事实标准封装好了所有细节RedissonClientredissonRedisson.create(config);RLocklockredisson.getLock(myLock);try{lock.lock();// 阻塞获取自动续期// 业务逻辑}finally{lock.unlock();}Redisson 提供了自动续期看门狗可重入同一线程多次获取不阻塞公平锁按申请顺序授予读写锁、信号量、CountDownLatch 等更复杂的同步原语集群模式下的 RedLock 实现Python 用redis-py自带的redis.lockGo 用redsync都能省掉不少基础工作。常见坑点1. 锁粒度太大把整个业务都放在锁里锁的持有时间过长吞吐量直线下降。锁应该只保护真正有竞争的那段代码。2. 锁粒度太小为了优化性能把锁拆得太细结果加多把锁反而引入死锁风险。粒度要适中。3. 忘了 unique_value释放锁时不校验持有者可能误删别人的锁。这是最常见的隐藏 bug。4. 没有重试机制加锁失败直接报错对业务不友好。一般要带退避重试但要设上限避免无限等待。5. 用普通 SET 代替 SETNXSET key value会无条件覆盖根本没有互斥语义。必须带 NX。实践建议能不用锁就不用优先考虑用 Redis 原子操作或乐观锁解决。生产环境用成熟库Redisson / redis-py 内置 lock别自己造轮子。TTL 要合理业务正常耗时的 2-3 倍即可配合看门狗续期。释放锁必须用 Lua校验持有者后再删除。强一致场景换工具ZooKeeper / etcd 比 Redis 更适合做分布式协调。监控锁的等待时间等待时间长说明竞争激烈要么优化业务要么调整锁粒度。分布式锁是个看起来简单实际复杂的话题。Redis 提供的命令很基础但要把锁用对必须考虑过期、续期、释放、容错等一系列问题。理解这些细节才能在真实业务里写出经得起考验的代码。