Sunday 的面试指南

Redis 分布式锁怎么实现?为什么加了锁仍然可能重复执行?

🧑‍💻 面试官:多个实例同时处理同一个订单,Redis 锁怎么加?

🙋‍♂️ 我:使用 SET 的 NX 和过期参数,取得锁以后才处理。

🧑‍💻 面试官:A 停顿太久,锁已经过期,B 拿到新锁了。A 恢复以后会自动停止吗?

🙋‍♂️ 我:不会,锁过期只是 Redis 中的状态变化,A 的代码可能继续运行。

🧑‍💻 面试官:那你怎么避免 A 删除 B 的锁?又怎么避免它们各处理一次订单?

锁要回答三件事:「谁持有、有效多久、过期后旧持有者还能做什么」。释放正确,不代表业务一定只执行一次。

面试速答(60 秒版)

Redis 分布式锁常见的基础做法,是用一条 SET 命令同时完成“不存在才设置”和“设置有效期”。锁值使用本次申请的唯一标识,释放时只有值仍属于自己,才原子删除。

但这只是有租期的互斥。锁过期以后,原来的程序不会自动停止;新持有者和旧持有者可能同时继续工作。续期能减少超时风险,也不能消除所有停顿和网络故障。

因此,涉及订单、扣款等操作,还需要数据库状态条件、唯一约束或幂等键保护。需要阻止过期持有者写入时,可以使用由可靠机制产生、并由资源端校验的递增 fencing token。

同时,Redis 主从异步复制和故障切换会影响锁安全。是否采用它,要按允许的故障与业务损失判断,不能把“加了锁”当成只执行一次的保证。

租期与业务只执行一次不同

知识点详解:锁过期以后,旧程序为什么还能执行?

先正确地取得和释放锁

假设多个实例都要处理订单 1001。锁 key 对应这个订单,每次申请生成不同的 owner 值。

申请时可以使用下面这种命令形式:

SET lock:order:1001 <本次唯一标识> NX PX 30000

NX 表示 key 不存在才设置,PX 设置毫秒有效期。把申请和过期放在同一条命令里,可以避免“先成功加锁,程序崩溃,来不及设置到期”的窗口。

释放时不能直接 DEL。服务需要原子比较当前值是否等于自己的 owner,再删除;否则 A 的锁过期后,可能删掉 B 新申请的锁。

可以使用相应版本支持的条件删除命令,或者在服务端脚本中完成比较与删除。Redis 官方分布式锁说明介绍了这一所有权检查。这里不依赖某个客户端库的默认行为,实际使用时仍要核对库与版本。

条件删除避免误删新锁

看一次超过租期的处理

假设 A 拿到 30 秒的锁,开始处理订单,随后进程停顿。

30 秒过去,Redis 删除过期 key。B 现在可以取得新锁并处理订单。但 Redis 不知道 A 的业务代码在哪里,也没有替它取消数据库请求。

A 恢复以后,如果继续执行,就可能与 B 同时写数据。A 就算不删 B 的锁,也仍可能完成一次重复处理。

所以,唯一 owner 解决的是“不误删别人的锁”,没有解决“过期持有者继续操作资源”。这两个问题必须分开。

续期为什么不是最后一道防线?

处理时间较长时,可以在确认 owner 仍属于自己后延长有效期。库里的自动续期也通常依赖类似检查。

但续期线程可能一起停顿,网络可能中断。续期失败以后,应用可以停止后续步骤,却不能认为已经发出去的操作一定被取消。

如果任务包含扣款,资源端应根据业务幂等键识别同一笔扣款请求,或者通过事务和状态条件保证只接受一次合法变更。重试拿到新锁,也不应该生成一份新的业务身份。

fencing token 怎样挡住旧持有者?

在一种可靠设计中,每次取得执行资格,会得到更大的 token。A 的 token 是 10,B 后来取得 11。

受保护的资源记录已经接受的最大 token。接受 11 后,再收到 10 的写入就拒绝。这样,检查发生在真正写资源的位置,而不只发生在应用发请求之前。

不过,token 必须由符合所需一致性的机制产生,资源端也必须原子校验。不能随便在 Redis 上做一次 INCR,就默认跨故障切换仍满足全部安全条件。

fencing 也不等于业务幂等:新持有者拿着更大 token,再重复处理同一订单,仍需要业务约束识别重复。

资源端递增token挡住旧持有者

面试官继续追问

Redis 主节点故障,切到副本后锁还安全吗?

单主异步复制不能保证锁已到副本。主节点刚接受 A 的锁就故障,副本可能还没有这条记录,B 会再次取得锁。

对于只用于减少重复查询的缓存重建,偶尔重复可能可接受;对于不能重复扣款的业务,不应仅依赖这种锁。需要按故障模型选择协调机制,并在业务资源端保留硬约束。

把过期时间设得很长,不就解决了吗?

它降低了正常处理超期的概率,却增加崩溃后的等待,而且无法覆盖所有异常停顿。

租期应该结合任务时长和恢复目标选择,但不能替代幂等与状态校验。没有合理上界的任务,更需要明确中断和资源端保护。

怎么测试锁设计?

模拟持锁实例停顿超过租期、释放时锁已换人、续期失败以及节点故障。

除了检查 Redis key,还要检查订单最终状态和副作用次数。只证明同一时刻 key 里只有一个 owner,并不能证明没有两个程序都完成业务。

面试速记卡

  • 申请:条件设置与过期一次完成。
  • 释放:原子比较 owner,不能直接删锁。
  • 租期:锁过期不会自动终止旧程序。
  • fencing:由资源端拒绝过期执行者,需要可靠 token 与原子校验。
  • 业务保护:幂等、状态条件和唯一约束不能被锁替代。
简历汪永久免费在线制作简历,模板直接套用、导出无水印,永久免费、下载免费,不需要付费解锁任何功能。去写简历