ZooKeeper 分布式锁实践(下):读写锁

栏目: 编程工具 · 发布时间: 7年前

内容简介:作者 | Sunny杏仁后端工程师,专注高并发和分布式编程,Golang爱好者。

作者 | Sunny

ZooKeeper 分布式锁实践(下):读写锁

杏仁后端工程师,专注高并发和分布式编程,Golang爱好者。

ZooKeeper 分布式锁实践(上篇)排它锁   中我们通过代码实践了如何使用 ZooKeeper 组件来实现排他锁。

排他锁简单易用,但是缺点也很明显:

  • 竞争压力大:当锁被占用之后,其他获取锁的操作只能阻塞等待;当锁释放后,所有等待锁的进程会在同一时刻争抢锁的使用权。

  • 羊群响应:锁释放后,会通知所有等待锁的进程,如果等待者特别多,一时间锁的竞争压力将会特别大。

简单来说就是: 通知范围太广、锁的粒度太大 。我们可以分别从这两个层面去寻找解决方案:

  • 缩小通知范围:等待锁的小伙伴们按先来后到的顺序排队吧,排好队了,接下来我只需要关心我前面一个节点的状态,当前一个节点被释放,我再去抢锁。

  • 缩小锁的粒度:锁不关心业务,但是可以简单地通过操作的读、写性质来二分锁的粒度:

    • 读锁:又称共享锁,如果前面没有写节点,可以直接上锁;当前面有写节点时,则等待距离自己最近的写节点释放( 删除 )。

    • 写锁:如果前面没有节点,可以直接上锁;如果前面有节点,则等待前一个节点释放( 删除 )。

    思考:为什么不是关注前面距离自己最近的写节点?

    如果两个写节点之间有读节点,必需等待读节点释放之后再进行写节点请求,否则会有不可重复读的问题。

数据结构 和排他锁一样,我们通过 ZooKeeper 的节点来表示一个读写锁的父节点,如 /SHARE_LOCK ,通过父节点下的临时自增子节点来表示一个读写操作请求,如 /SHARE_LOCK/R_0000000001  。整体数据结构如下图所示。

ZooKeeper 分布式锁实践(下):读写锁

算法

获取锁

获取锁的算法步骤:

  1. 开始尝试获取锁

  2. 如果持久化父节点不存在,则创建父节点

  3. 如果当前临时自增子节点不存在,则创建子节点

  4. 获取父节点下的所有子节点

  5. 在所有子节点中,查找序号比当前子节点小的前置子节点( 最近的兄节点 )有两种情况:

  • 读请求:查找比自己小的前置**写**子节点 ( 最近的兄节点 )

  • 写请求:查找比自己小的前置子节点 ( 最近的兄节点 )

  • 如果没有更小的前置子节点,则持有锁

  • 如果有更小的前置子节点,则监听该子节点被释放( 删除 )的事件

  • 释放 ( 删除 )子节点事件被触发后,重复第 1 步

  • 释放锁

    释放锁的算法与排他锁部分的释放锁算法相似,这里不再赘述。

    加锁、解锁流程

    加锁、解锁完整的流程图。

    ZooKeeper 分布式锁实践(下):读写锁

    代码实现

    子节点定义

    子节点属性

    • lockName  读写锁的名称,即父节点的名称

    • name  子节点的名称,格式为 : {请求类型:R/W}_{自增序号}   ( 子节点的路径为: {lockName}/{name}  

    • seq  子节点的自增序号,通过解析   name   属性   _   下划线分隔符后面的数字字符串来获取序号( ZooKeeper 创建临时自增节点时会自动分配 Int 范围内的序号 )

    • isWrite  子节点是否为写请求,通过解析   name   属性   _   下划线分隔符前面的英文字符来判断请求类型 :

      • R :读请求

      • W :写请求

    ZooKeeper 分布式锁实践(下):读写锁

    读写锁定义和初始设置

    读写锁的属性

    • lockName  读写锁的名称,即父节点的路径

    • locker  获取锁的请求方,即锁的持有者,释放锁时需要验证请求者与锁的持有者是否一致

    • isWrite  请求类型:

      • 读: false

      • 写: true

    读写锁的初始设置

    • 连接到 ZooKeeper 实例

    • 连接后,如果父节点不存在,则创建父节点

    ZooKeeper 分布式锁实践(下):读写锁

    尝试获取锁

    尝试获取锁的算法实现

    1. 获取或创建 ZooKeeper 子节点

    2. 获取当前子节点后,遍历所有的子节点,查找:

    • front  离当前子节点最近的兄节点:序号比当前子节点的序号小、且在小于当前序号的子节点中序号是最大的

    • fontWrite  离当前子节点最近的写、兄节点:序号比当前子节点的序号小、且在小于当前序号的子节点中序号是最大的、且为写子节点

  • 查找后,返回序号更小的兄节点:

    • 读请求:返回最近的写、兄节点,用于 Watch 监听释放( 删除 )事件

    • 写请求:返回最近的兄节点,用于 Watch 监听释放( 删除 )事件

  • 如果没有更小的子节点,返回 None   ,表示成功地获取了锁

  • ZooKeeper 分布式锁实践(下):读写锁

    ZooKeeper 分布式锁实践(下):读写锁

    同步获取锁

    同步获取锁的算法实现

    1. 尝试获取锁

    2. 如果没有兄节点,则成功持有锁

    3. 如果得到更小的兄节点,则监听该兄节点的释放( 删除 )事件

    4. 收到兄节点的释放( 删除 )事件通知后,重复第 1 步

    ZooKeeper 分布式锁实践(下):读写锁

    测试验证

    最后,通过一个简单的测试方法来验证读写锁的加、解锁过程:

    ZooKeeper 分布式锁实践(下):读写锁

    测试结果

    ZooKeeper 分布式锁实践(下):读写锁

    测试结果、分析

    # 请求方 操作 输出
    1 LOCK1_读 加锁:成功 [LOCK1] : Lock
    2 LOCK2_写 加锁:等待,因为有未释放的兄节点 LOCK1
    3 LOCK3_写 加锁:等待,因为有未释放的兄节点 LOCK2
    4 LOCK4_读 加锁:等待,因为有未释放的兄、写节点 LOCK3
    5 LOCK5_读 加锁:等待,因为有未释放的兄、写节点 LOCK3
    6 LOCK1_读 解锁:成功,通知到 LOCK2 [LOCK1] : Unlock
    7 LOCK2_写 收到通知,尝试加锁:成功 [LOCK2] : Lock
    8 LOCK2_写 解锁:成功,通知到 LOCK3 [LOCK2] : Unlock
    9 LOCK3_写 收到通知,加锁成功 [LOCK3] : Lock
    10 LOCK3_写 解锁成功,通知到 LOCK4、LOCK5 [LOCK3] : Unlock
    11 LOCK4_读 收到通知,尝试加锁:成功 [LOCK4] : Lock
    12 LOCK5_读 收到通知,尝试加锁:成功 [LOCK5] : Lock
    13 LOCK4_读 解锁:成功 [LOCK4] : Unlock
    14 LOCK5_读 解锁:成功 [LOCK5] : Unlock

    尾声

    通过 ZooKeeper 分布式锁实践,对它的接口最直观的感受就是 简单 。虽然它没有直接提供加锁、解锁这样的原语,但是当你了解了它的数据结构、接口和事件设计之后,加锁、解锁功能简直呼之欲出,实现起来毫无障碍,一切都是那么地合理、妥当。

    而 ZooKeeper 的能力远不止于此,就像前面提到的它能够十分轻松地实现诸如:数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁、分布式队列这些小菜,不得不佩服 ZooKeeper 设计者的抽象能力。本篇只是浅尝了 ZooKeeper 的基本能力,有关它的设计思路、实现细节仍待进一步发掘和探索。

    全文完

    以下文章您可能也会感兴趣:

    我们正在招聘 Java 工程师,欢迎有兴趣的同学投递简历到 rd-hr@xingren.com 。

    ZooKeeper 分布式锁实践(下):读写锁

    杏仁技术站

    长按左侧二维码关注我们,这里有一群热血青年期待着与您相会。


    以上所述就是小编给大家介绍的《ZooKeeper 分布式锁实践(下):读写锁》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

    查看所有标签

    猜你喜欢:

    本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

    Domain-Driven Design

    Domain-Driven Design

    Eric Evans / Addison-Wesley Professional / 2003-8-30 / USD 74.99

    "Eric Evans has written a fantastic book on how you can make the design of your software match your mental model of the problem domain you are addressing. "His book is very compatible with XP. It is n......一起来看看 《Domain-Driven Design》 这本书的介绍吧!

    JS 压缩/解压工具
    JS 压缩/解压工具

    在线压缩/解压 JS 代码

    HTML 编码/解码
    HTML 编码/解码

    HTML 编码/解码

    RGB CMYK 转换工具
    RGB CMYK 转换工具

    RGB CMYK 互转工具