分布式锁如何工作?
未标记
考查重点:共享互斥资格与租期失效。多进程向共同协调器原子抢资源资格,持有者执行后释放;租期失效旧任务可能仍活,需业务幂等/隔离
本题目录
参考答案
核心回答
分布式锁是让多个进程在操作同一份共享资源前,先到一个共同的协调系统中竞争访问资格。协调系统原子地决定谁获得锁,获得者执行临界区,其他请求等待、重试或失败,最后由持有者释放。 Redis、ZooKeeper、数据库都可以承载这种协调,但故障与失效语义不同。
例如,同一个缓存条目过期后,多个服务实例可能同时回源重建。本地锁只能约束本进程中的线程;让这些实例竞争同一个业务 key 对应的分布式锁,才有机会把重建合并为一次。锁只记录协调状态,真正读取数据库和写入缓存的仍然是业务进程。
它能提供的互斥还受租约、会话和故障模型约束。拿到过锁,不代表客户端可以无限期执行;锁过期或会话失效后,旧进程仍可能继续运行,需要业务资源一起处理这类风险。
正常流程怎样工作
- 按共享资源确定锁名。 同一资源的访问方使用同一个锁名,不相关资源尽量分开,避免一把全局锁把所有请求串行化。绕过加锁协议的写入,不会自动受到保护。
- 原子获取并标识持有者。 不能先查询“没有锁”,再单独写“我持有锁”,否则多个进程可能同时成功。还要区分本次获取与其他进程、其他轮次的获取,方便安全释放和续约。
- 在有效持锁期间执行完整临界区。 拿到锁后重新检查业务状态,避免使用等待前的旧数据;需要保护数据库事务时,通常应覆盖到事务真正提交,而不只是锁住查询或发起异步任务的瞬间。
- 只释放自己仍然持有的锁。 校验身份与释放必须在协调系统中原子完成,否则校验后锁过期、被别人重新获取,本次删除就可能误删新锁。失败、异常退出与正常路径都要考虑释放。
获取失败时,应明确立即失败、有限等待还是退避重试。加锁请求超时则意味着结果可能未知,不能一边认定失败,一边继续执行受保护的业务。
常见实现有什么区别
Redis 通常用原子条件写入保存锁名、唯一持有标识和 TTL,避免客户端崩溃后永久占锁。释放时原子地比较标识再删除,续约也只应更新当前仍属于自己的锁。详细命令和 Redis 版本行为可继续看Redis 分布式锁。
ZooKeeper 的常见方案是在同一个父节点下创建临时顺序节点,序号最小的客户端获得资格;其余客户端监听紧邻自己的前驱节点,收到删除通知后重新检查。前驱已经消失时要重新读取,不能错过通知后一直等待。监听前驱可以减少锁释放时所有等待者一起抢占的“羊群效应”。
ZooKeeper 的临时节点绑定的是会话:显式释放时删除自己创建的节点,会话过期后由集群清理。短暂断网不等于节点立刻消失,而集群确认会话过期时,失联客户端也可能还不知道这一结果。
数据库 也能通过事务行锁、唯一约束或条件更新实现部分协调需求。如果资源和约束本来就在同一数据库中,优先判断这些能力是否已足够。例如防止库存扣成负数,可以用受条件约束的原子更新;没必要仅仅因为部署了多个实例,就额外引入一套锁服务。
租约过期后,旧持有者怎么办
租约或会话机制解决的是“持有者失联后,其他人最终还能继续”的问题,但不会强制停止旧进程。典型窗口是:A 获锁后长时间暂停,锁已失效,B 获得新锁开始执行;A 恢复后仍按原计划写业务资源。
续约能减少正常长任务提前失锁的概率,但续约线程也可能被暂停或断网。客户端发现失租后应停止后续受保护操作,已经发出的远程请求却未必能撤回。因此,唯一随机标识加原子释放,只能避免 A 误删 B 的锁,不能单独拦住 A 的迟到业务写。
正确性要求更高时,可以采用 fencing token(隔离令牌):每轮有效授予产生递增代次,业务写入携带代次,资源端原子地检查并记录已接受的代次,拒绝更旧的写。例如 B 已用代次 8 更新资源后,A 携带代次 7 的请求再到达,就会被资源拒绝。
这个代次要由满足要求的协调机制产生,故障后不能随意倒退;资源端也必须真正参与校验。当前代次可能有多次合法写入,重复请求是否允许还要由业务幂等规则决定,不能仅靠“每次必须比上次更大”笼统处理。
常见追问
Redis 主从切换后,锁还可靠吗?
异步复制存在窗口:锁写入主节点成功,但尚未复制就发生切换,新主节点上可能没有这把锁,另一客户端便可能获得它。增加副本或自动续约不能直接消除这个窗口。应根据互斥失效的业务代价选择协调方案,并用资源端约束、版本校验或幂等处理补足所需保证。
怎样避免死锁和一直等不到锁?
正常路径和异常路径都释放锁,客户端崩溃由租约或会话失效处理;获取设置合理的等待上限。若同时获取多把锁,还应统一顺序、减少嵌套,避免循环等待。TTL 只能帮助锁最终失效,不会自动修正业务中的所有死锁与长时间阻塞。
分布式锁能保证订单写入和消息发送一起成功吗?
不能。锁主要协调并发进入,事务原子性和故障恢复仍需单独设计。数据库事务、唯一约束、幂等、状态机,以及可靠事件记录等各自解决不同问题,不能用“一把锁”代替。
参考资料
- 国内:JavaGuide:分布式锁入门:多实例访问外部共享数据、锁粒度和临界区部分,解释本地锁的边界,以及为什么数据库约束有时已经足够。
- 国内:JavaGuide:分布式锁常见实现方案:参考 Redis 原子获取释放、ZooKeeper 临时顺序节点和 fencing,比较正文中协调锁资格与资源端拒绝旧写入的不同职责。
- 国内:advanced-java:Redis 与 ZooKeeper 分布式锁:唯一持有标识、超期误释放和异步复制切换的例子,说明锁不仅要考虑正常获取,还要考虑失效后的行为。
- 国外:Redis:Distributed Locks with Redis:单实例获取释放、租约有效期、故障切换与 fencing 讨论,解释安全释放为什么不能代替迟到写防护。
- 国外:Apache ZooKeeper:Recipes — Locks:创建临时顺序节点、监听前驱、重新检查与创建结果未知的处理过程,说明协调客户端怎样获得和释放资格。




