Redis分布式锁Redlock解决击穿死锁高可用方案

作者:技术拾光时间:2026-07-20 08:35:14浏览:556

Redlock无法解决缓存击穿与死锁,仅降低单点故障风险。击穿防护需业务层设计,死锁依赖超时控制与看门狗机制。高可用要求节点独立部署,推荐使用Redisson的RedissonRedLock,避免手动实现导致漏清理或时钟漂移问题。

聊 Redlock 的时候,大家最容易踩的坑就是把它当成万能药。实际上,Redlock 既不能解决缓存击穿,也防不住死锁——它的核心价值只在于降低单点故障带来的锁丢失风险。真正的击穿和死锁防护,还得靠业务层的精心设计、超时控制,以及看门狗机制来兜底。

Redlock 压根不碰缓存击穿这件事

先厘清概念:缓存击穿,说的是某个热点 key 在过期的一瞬间,大量请求绕过缓存直接打到数据库。Redlock 是什么?它是一个分布式互斥工具,跟缓存策略八竿子打不着。它对 key 是否过期、缓存是否命中完全无感。

常见误解是:用 Redlock 把“查库 + 回填缓存”这段逻辑锁住,就能防击穿。但问题出在哪?

  • SET key value NX PX 30000 这类加锁命令本身跟缓存 key 的生命周期没有任何绑定关系。
  • 如果业务里先删缓存再加锁查库,而删缓存操作又不是原子的,那么 Redlock 根本拦不住并发回源请求。
  • 加锁失败时,Redlock 只是抛异常或返回 false,至于你要阻塞重试、降级返回还是熔断——它一概不管,内部没有内置任何重试或兜底逻辑。

死锁?Redlock 只是让死锁更难发生,而非杜绝

Redlock 的锁自动过期依赖每个节点上 PX 设定的 TTL,但它无法规避下面这些死锁场景:

  • 客户端拿到锁后,业务逻辑里卡住了(比如死循环、Full GC、线程挂起),既没完成操作也没主动解锁,只能干等 TTL 过期。
  • 网络分区导致部分节点响应超时,客户端误判加锁失败,但某些节点其实已经成功设锁——这时候如果没有执行 unlockInner() 清理,锁就会残留。
  • 多个 Redlock 实例之间没有任何协调机制,A 锁等着 B 锁,B 锁等着 A 锁,这种环形依赖 Redlock 既检测不到,也不会自动打破。

真正防死锁,得靠这些东西:

  • 所有加锁调用必须带上 waitTime 和 leaseTime(就像 Redisson 的 tryLock(3, 30, TimeUnit.SECONDS) 那样)。
  • 业务代码里要有明确的超时边界,比如用 CompletableFuture.orTimeout() 包裹核心流程。
  • 监控 redis.call('pttl', KEYS[1]) 的返回值,在续期之前确认锁仍然有效。

高可用的前提:节点必须真正独立

很多人部署了 5 个 Redis 实例,却把它们塞在同一台物理机上、同一个 Docker Compose 网络里,甚至共用一个配置中心——这根本不是 Redlock 所要求的“独立性”。一旦宿主机宕机,5 个节点全部跪掉,多数派瞬间归零。

必须满足以下条件:

  • 5 个节点分布在至少 3 个不同的可用区(AZ),不能跨 AZ 共享存储或网络平面。
  • 每个节点使用独立的 redis.conf,禁用 slaveof、replicaof,不开启 AOF 或者只使用 appendfsync no。
  • 客户端连接串不能写死 IP,要用 DNS 或服务发现(如 Nacos)来动态感知节点健康状态。
  • 加锁时的超时时间(比如 commandTimeout)必须远远小于锁的 TTL,建议 ≤ TTL/3,否则网络一抖动就导致多数派失败。

生产环境别手撸 Redlock,直接用 Redisson 的 RedissonRedLock

自己实现 Redlock 很容易漏掉关键细节:

  • 加锁失败后没有对已成功节点执行 EVAL ... DEL 进行清理,结果锁残留。
  • 用 System.currentTimeMillis() 来计算耗时,却忽略时钟漂移——跨机房部署时 NTP 同步误差可能达到上百毫秒。
  • 解锁时只发给“加锁成功的节点”,但 Redlock 的规范要求的是向全部 N 个节点发送 Lua 脚本,不管之前是否加锁成功。

Redisson 的 RedissonRedLock 已经把这些逻辑都封装好了:

RLock lock1 = redisson.getLock("lock1");
RLock lock2 = redisson.getLock("lock2");
RLock lock3 = redisson.getLock("lock3");
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
// 同时向 3 个节点申请,至少 2 个成功才算获取锁
boolean isLocked = redLock.tryLock(10, 30, TimeUnit.SECONDS);

注意:tryLock(10, 30, ...) 里第一个 10 是最大等待时间,第二个 30 是 leaseTime(看门狗初始 TTL),不是过期时间——看门狗会自动续期,直到 unlock() 或线程终止。

最容易被忽略的一点是:Redlock 的可靠性提升是有代价的。5 节点 Redlock 的 P99 加锁延迟通常是单节点的 3~5 倍,而且吞吐量下降明显。如果业务能接受短暂的不一致(比如库存超卖容忍 0.001%),那么用单节点 + Redisson 看门狗 + 本地限流,往往比 Redlock 更稳定、更快、也更省资源。