当前位置: 首页 > news >正文

Zookeeper分布式锁原理

核心基础

在理解原理前,先回顾三个 ZooKeeper 的核心特性:

  1. 临时节点(Ephemeral Nodes):创建该节点的客户端会话(Session)一旦失效(如断开连接),那么这个节点会被自动删除。这保证了即使客户端崩溃,锁也能被自动释放,避免了死锁。
  2. 顺序节点(Sequential Nodes):创建节点时,ZooKeeper 会自动在节点名称后附加一个单调递增的序列号(如 lock-0000000001)。这保证了所有节点的创建顺序是全局唯一的。
  3. Watcher 机制:客户端可以在某个节点上设置监听(Watcher)。当该节点被删除、修改等事件发生时,ZooKeeper 会通知所有设置了 Watcher 的客户端。这是实现客户端间高效协调和通知的关键。

分布式锁的实现流程(以排他锁为例)

假设我们有一个锁的根节点 /locks/my_lock

1. 获取锁(Acquire Lock)
  • 步骤一:创建临时顺序节点
    所有想要获取锁的客户端,都会在 /locks/my_lock 下创建一个临时顺序节点,例如:

    • Client A 创建了 /locks/my_lock/lock-0000000001
    • Client B 创建了 /locks/my_lock/lock-0000000002
    • Client C 创建了 /locks/my_lock/lock-0000000003
  • 步骤二:检查自己是否是最小节点
    每个客户端都会获取 /locks/my_lock 下的所有子节点,并按序列号排序。

    • 对于 Client A:它发现自己的节点 lock-0000000001 是序列号最小的节点。那么它就成功获得了锁,可以执行自己的业务逻辑。
    • 对于 Client B:它发现自己的节点 lock-0000000002 不是最小的,前面还有 lock-0000000001。这意味着锁正被 Client A 持有。
    • 对于 Client C:同样,它发现自己不是最小的,锁不可用。
  • 步骤三:监听前一个节点(等待锁)
    Client B 和 Client C 不会不停地轮询询问“轮到我了吗?”。而是采用更高效的方式:

    • Client B:它会向它前面的那个节点(即 lock-0000000001)设置一个 Watcher 监听
    • Client C:它会向它前面的那个节点(即 lock-0000000002)设置一个 Watcher 监听

    这样,每个客户端都只监听它前面的节点,形成一个等待队列。

整个过程如下图所示,它清晰地展示了客户端如何通过创建节点、排序和监听来形成一个有序的等待队列:

请添加图片描述

2. 释放锁(Release Lock)
  • 情况一:正常释放
    Client A 完成业务逻辑后,主动删除它创建的那个临时节点 lock-0000000001

  • 情况二:异常释放
    如果 Client A 在持有锁期间进程崩溃或网络断开,由于它创建的是临时节点,ZooKeeper 会自动检测到会话失效,并自动删除 lock-0000000001

3. 唤醒下一个等待者(Notify Next)
  • lock-0000000001 被删除(无论是主动还是被动)时,ZooKeeper 会通知所有监听了这个节点的客户端。也就是 Client B 会收到一个通知
  • Client B 被唤醒后,它会重复“步骤二”:再次获取 /locks/my_lock 下的所有子节点。
    • 此时子节点是 [lock-0000000002, lock-0000000003]
    • Client B 发现自己的节点 lock-0000000002 现在是最小的了!于是它成功获得了锁。
  • 同理,当 Client B 释放锁,删除 lock-0000000002 后,Client C 会被唤醒,进而获得锁。

这种设计的精妙之处

  1. 避免羊群效应(Herd Effect)
    传统的做法是所有客户端都监听同一个锁节点,释放时所有客户端都被唤醒并同时争抢,给 ZooKeeper 和服务端带来巨大压力。而只监听前一个节点的方式,每次锁释放都只精确地唤醒一个客户端(队列中的下一个),压力非常小。

  2. 天然的公平锁(Fair Lock)
    由于节点顺序是全局唯一的,并且严格按照顺序唤醒,所以每个客户端获取锁的顺序就是它们创建节点的顺序,实现了先来后到的公平性。

  3. 自动防死锁(Deadlock-Free)
    得益于临时节点的特性,任何客户端持有的锁都会在会话结束时自动释放,无需担心因为客户端宕机而导致的锁永久死锁问题。

  4. 可重入性(Reentrancy)(需要客户端实现):
    如果同一个客户端线程想再次获取锁,可以在客户端内存中记录一个计数器(count)。检查锁的持有者时,不仅要看节点序列号,还要看节点名称中是否包含自己的客户端ID。如果是自己持有的,就直接增加计数器并返回成功,而无需再创建节点。


总结:ZooKeeper 分布式锁的核心步骤

步骤操作说明
1. 争抢所有客户端在锁目录下创建临时顺序节点宣告自己的排队资格。
2. 判断获取锁目录下所有子节点,判断自己是否是序号最小的节点。是则成功获锁;否则继续。
3. 等待如果不是最小,则监听自己前面那个节点的删除事件。避免羊群效应,高效等待。
4. 释放业务处理完,主动删除自己创建的那个节点。正常释放锁。
5. 唤醒节点被删除,ZooKeeper 通知下一个监听它的客户端下一个客户端被唤醒,回到步骤2。

在实际开发中,我们通常不直接使用 ZooKeeper 的原生 API 来实现锁,而是使用更高级的封装库,例如 Apache Curator。Curator 提供了一个成熟、稳健的分布式锁实现(InterProcessMutex),直接开箱即用,避免了手动处理各种极端情况(例如连接丢失、重试等)。但理解其底层原理对于诊断问题和设计系统至关重要。

http://www.xdnf.cn/news/1430731.html

相关文章:

  • 域名备案成功后怎么还显示没有注册
  • 基于vue3和springboot框架集成websocket
  • springboot项目使用websocket功能,使用了nginx反向代理后连接失败问题解决
  • DASK shuffle任务图分析
  • ansible循环
  • 零依赖每月工作计划备忘录:高效管理你的每一天
  • TSMC-1987《Convergence Theory for Fuzzy c-Means: Counterexamples and Repairs》
  • 电动车动力电池自动点焊机|深圳比斯特自动化
  • 证明有理数集不是完备的度量空间
  • SpringBoot 整合 RabbitMQ 的完美实践
  • 【代码随想录day 22】 力扣 40.组合总和II
  • Elasticsearch 深分页限制与解决方案
  • 计算机Python毕业设计推荐:基于Django+Vue用户评论挖掘旅游系统
  • 深度学习——基于卷积神经网络实现食物图像分类之(保存最优模型)
  • 前缀和之距离和
  • 架构设计:AIGC 新规下 UGC 平台内容审核防火墙的构建
  • 【XR技术概念科普】什么是注视点渲染(Foveated Rendering)?为什么Vision Pro离不开它?
  • A股大盘数据-20250902分析
  • 深入浅出 RabbitMQ-消息可靠性投递
  • 学习日记-SpringMVC-day48-9.2
  • WPF应用程序资源和样式的使用示例
  • 洗衣店小程序的设计与实现
  • 深度学习篇---DenseNet网络结构
  • gitlab中回退代码,CI / CD 联系运维同事处理
  • VR森林经营模拟体验带动旅游经济发展
  • Time-MOE 音频序列分类任务
  • 【C++框架#2】gflags 和 gtest 安装使用
  • Redis 的跳跃表:像商场多层导航系统一样的有序结构
  • 疯狂星期四文案网第58天运营日记
  • 大模型微调数据准备全指南:清洗、标注与高质量训练集构造实战