面灵AI→

京东 Java开发 三面:Redis 分布式锁在主从切换下为什么会失效

轮次
三面
时间
2026-10
来源
牛客网

《面试题目》

  1. (一面)围绕项目架构与分库分表做了追问。(原帖未记录具体问法)
  2. (一面)手撕一道中等难度算法题。(原帖未记录具体题面)
  3. (三面)你们用的 Redis 分布式锁,如果主节点宕机、锁还没同步到从节点,新的主节点上来后,别的线程是不是就能拿到锁了?你怎么解决?
  4. (三面追问)看门狗续期为什么防不住主从异步复制导致的锁丢失?RedLock 基于时钟,隐患在哪里?

《参考解析》

锁丢失的根因是「加锁」和「复制」不在同一个一致性协议里。 Redis 主从是异步复制:客户端在 master 上 SET key value NX PX ttl 成功,master 返回成功时这个 key 还没同步到 replica;master 宕机后哨兵把 replica 提升为新 master,新 master 上没有这把锁,另一个客户端用同样的 key 就能加锁成功。此刻旧客户端仍认为自己是持有者,于是两个客户端同时进入临界区。看门狗只能定期给旧 master 上的 key 续期,既防不住切换,也可能在客户端长时间 STW 或网络分区时继续「以为」自己持有锁。这不是 Redisson 的实现 bug,而是 AP 系统用租约模拟互斥的固有缺口。

Redlock 想用多数派解决单点,但没有解决「持有者已经过期还在写」。 Redlock 在 N 个独立 master 上依次加锁,超过半数成功且总耗时小于锁有效期才算成功。Kleppmann 的批评是它依赖时钟与网络延迟假设:进程 GC 停顿、网络抖动或时钟漂移可能让客户端在锁实际过期后继续操作共享资源,而此时另一个客户端已经合法获得锁。antirez 的回应是这些假设在工程上可控,且可以通过检查耗时、重试和补偿降低概率。争论的核心其实是「你要的是效率还是正确性」:对「重复写会坏数据、重复扣款」的场景,不能把正确性押在锁上。

真正的解法是 fencing token:让被保护的资源自己拒绝过期持有者。 锁服务每次授予一个单调递增的 token 或版本号,客户端带着 token 写资源,存储侧记录已见到的最大 token,小于等于它的写一律拒绝。这样即使旧持有者在锁过期后醒来,它的写也会被挡掉。实现上可以是 ZooKeeper 的 zxid 或顺序节点、etcd 的 lease 加 revision、数据库里的版本列,或者业务表的 version 条件更新。代价是资源侧必须参与校验,不能只改锁客户端;这也意味着锁从正确性机制降级为「减少冲突的优化」,最终一致性由幂等和版本控制兜底。

选型要把 CP 与 AP 的代价讲清楚。 Redis 主从加哨兵是 AP:延迟低、吞吐高、故障时可能丢锁;ZooKeeper 和 etcd 是 CP:用 quorum 写保证同一时刻只有一个持有者,但延迟更高、运维更重、分区时可能不可用。可以给一个分层:短临界区、可幂等的任务用 Redis 锁加幂等键;涉及资金、库存、权限变更的临界区用 etcd、ZK 或数据库行锁与唯一约束;跨服务的长事务用状态机加补偿,而不是一把大锁。还要提锁粒度、TTL 与续租、释放时用 Lua 校验持有者、以及锁等待超时,避免把「丢锁」换成「死锁」。

幂等兜底要落到数据和接口两层。 数据层用唯一约束挡住重复插入,用条件更新(例如 UPDATE ... WHERE status = 'PENDING')让只有一个执行者能推进状态;接口层让每个请求带客户端生成的幂等键,服务端持久化「键—结果」,重复请求直接返回同一结果。再配状态机、执行版本号、租约与超时回收,处理 worker 崩溃后的恢复;对已经产生的副作用(发了消息、扣了款)要有对账和补偿。面试里能把「锁—fencing—幂等—对账」串成一条链,比背 Redlock 的定义更能证明你理解边界。

回答这类题的结构比结论更重要。 先承认失效场景真实存在,再区分「锁是效率优化还是正确性保证」,然后给出 fencing token 和幂等两道防线,最后说清为什么没有银弹:Redis 快但可能丢锁,ZK 稳但代价高,业务侧幂等才是最终兜底。五年经验被问倒,通常不是因为没听过 Redlock,而是因为只答了「用 Redisson 看门狗」,没有回答「锁失效后数据怎么办」。