Skip to content

Redis 实现分布式锁时可能遇到的问题有哪些? ​

  • 标签:后端、Redis

🏛 业务架构师 ​

Redis 分布式锁的坑我按业务后果排序列讲:

  • 锁过期但业务没执行完:GC 停顿或慢 SQL 让临界区超时,锁自动释放、B 进来,A 恢复后继续写——两个线程同时操作同一订单,数据错乱。解法:Redisson 看门狗自动续期,或超时时间按 P99×3 设置;
  • 误删他人锁:A 执行完准备删锁,恰好锁已过期被 B 持有,A 一删把 B 的锁删了。解法:value 存唯一标识 + Lua 原子"验值再删";
  • 主从切换丢锁:加锁写主成功、还没同步到从就宕机,新主没有这把锁,B 又能加锁——两个客户端同时持锁。Redisson 的 RedLock 方案争议很大(时钟漂移、多数节点部署成本),Martin Kleppmann 与 Antirez 的论战结论是:对正确性要求极高的场景,RedLock 不如用 Zookeeper/etcd 这类共识系统;
  • 不可重入与锁内异常:同线程嵌套加锁死锁、异常没释放锁——Redisson 可重入 hash 结构 + finally 释放解决。

架构结论:先分级再选型——防重复提交这类"锁失效只是多算一次"的场景,Redis 单实例锁 + 幂等兜底足够;资金划转这类"锁失效即资损"的,用 Zookeeper 临时顺序节点或干脆数据库悲观锁串行化。锁的可靠性按业务代价定价,没有全场景最优解。

🏗 系统架构师 ​

单靠 SET NX PX 不够,生产级 Redis 锁要闯四关,我按故障场景讲:

  1. 误删他人锁:A 的锁过期被 B 拿到,A 执行完 DEL 把 B 的锁删了 → 释放前 Lua 脚本比对 value,只删自己的;
  2. 业务超时锁先过期:临界区 30 秒锁 10 秒,两个客户端同时进临界区 → 看门狗后台续期(Redisson 每锁寿命 1/3 时间自动续),或按业务 P99×2 设置过期并做幂等兜底;
  3. 主从切换锁丢失:加锁写在主、异步复制未到从,主宕机新主无此锁,第二个客户端加锁成功 → 互斥被破坏。RedLock 多实例多数派是理论解,但工程上争议大;我的选择:能容忍极小概率互斥破坏的业务用单 Redis + 幂等兜底;不能容忍的上 ZooKeeper(临时顺序节点 +羊群规避)或 etcd;
  4. 不可重入:同线程递归调用死锁自己 → Redisson 用 hash 结构(field 存线程标识、value 存次数)实现可重入。

额外工程点:锁要有获取超时与重试上限,失败快速返回让上游限流;监控锁竞争激烈度,竞争过高说明锁粒度太粗,该拆分或改队列串行化。

💻 全栈开发专家 ​

我按"问题 → 解法"的链式来答(这也是真实演进过程):

  1. 锁过期但业务没执行完:10s 过期,GC 停顿/慢 SQL 导致超时,锁被别人拿走 → 并发执行。解法:看门狗续期(Redisson 默认 30s,每 10s 检查持有则续到 30s)。
  2. 误删别人的锁:自己锁过期后去 DEL,删掉的是新持有者的锁。解法:value 存唯一标识,Lua 脚本"比对再删"。
  3. 不可重入:同线程嵌套加锁死锁。解法:Redisson 用 hash 结构存 threadId → 重入次数。
  4. 主从切换丢锁:异步复制,master 挂了锁还没同步到 slave。理论解 RedLock(多数派),但有争议(Martin Kleppmann vs Antirez 之争),运维复杂。我的实践取舍:非资金场景接受概率(加 fencing token/乐观锁兜底),强正确性场景换 ZK/etcd。
  5. 无等待队列:拿不到锁直接失败。解法:Redisson 的 pub/sub 通知 + 等待队列,避免自旋打 Redis。

最后必须补一句:分布式锁只是并发控制的一环,业务的最终防线永远是 DB 乐观锁/唯一索引——锁丢失最多导致重复请求,唯一索引保证不产生脏数据。