美文网首页
Zookeeper学习-08 Zookeeper 分布式锁

Zookeeper学习-08 Zookeeper 分布式锁

作者: bocsoft | 来源:发表于2020-04-09 16:46 被阅读0次

    一、设计

    使用临时顺序znode来表示获取锁的请求,创建最小后缀数字znode的用户成功拿到锁。


    01 设计.png

    二、避免羊群效应(herd effect)

    把锁请求者按照后缀数字进行排队,后缀数字小的锁请求者先获取锁。如果所有的锁请求者都watch锁持有者,当代表锁持有者的znode被删除后,所有的锁请求者都会都会通知到(惊着了),但是只有一个锁请求者能拿到锁。这就是羊群效应。


    02锁持有者.png

    三、代码结构

    03代码结构.png
    1. org.apache.zookeeper.recipes.lock.ZNodeName:临时节点名称,能够根据sequence进行排序。实现了Comparable接口。名称中以'-'分割,并且在构造函数中提取sequence值。compareTo方法中,先根据sequence比较,如果相等,再根据前缀prefix比较:


      04 compareTo.png
    2. org.apache.zookeeper.recipes.lock.ZooKeeperOperation:锁实现接口。并且能够实现重试操作。主要实现类为org.apache.zookeeper.recipes.lock.WriteLock中的私有内部类:org.apache.zookeeper.recipes.lock.WriteLock.LockZooKeeperOperation,为实际的获取锁操作类。

    3. org.apache.zookeeper.recipes.lock.LockListener:锁监控接口,定义了获取锁和释放锁的回调方法。

    4. org.apache.zookeeper.recipes.lock.ProtocolSupport:主要提供retryOperation等同步操作。

    5. org.apache.zookeeper.recipes.lock.WriteLock:互斥写锁的主要实现,主要是选举一个leader节点。通过调用lock()方法来尝试获取锁。可以注册一个监听器LockListener在获取锁或者是释放锁的时候调用。也可以通过调用 isOwner()来询问是否拥有锁:


      05 lock.png

    四、调试跟踪

    为了避免超时,更改org.apache.zookeeper.test.ClientBase类中的超时时间设置:


    06 clientBase.png

    进入org.apache.zookeeper.recipes.lock.WriteLock中的lock()方法:


    07 断点lock方法.png

    进入org.apache.zookeeper.recipes.lock.ProtocolSupport中的ensureExists方法,判断目录是否存在:


    08 断点ensureExists方法.png

    如果不存在,则创建,且创建模式为PERSISTENT


    09 PERSISTENT.png 10 断点ensureExists方法.png

    进入retryOperation方法,通过重试机制(默认重试次数retryCount为10次)执行接口ZooKeeperOperation实现类的execute()方法,


    11 断点retryOperation方法.png

    具体为执行org.apache.zookeeper.recipes.lock.WriteLock.LockZooKeeperOperation类的execute()方法
    努力尝试查找最小后缀数字的znode节点成功拿到锁。


    12 execute方法.png
    13 锁判断.png
    然后再创建一个客户端,再次进行加锁:
    14 执行另一个锁.png
    15 另一个锁目录判断.png
    16案例执行成功.png

    五、相关问题

    1. 对于分布式锁的场景,如果创建的是临时节点,当T1请求获取锁后,执行相应业务逻辑,但是此时业务逻辑还没有执行完成,因网络原因导致session过期,临时节点就会被服务端删除。这样的话,其他节点也可以获取锁,分布式锁就被破坏了。对于此种问题,可以通过创建持久性节点来解决。
    2. znode是否类似于Redis中的Key的概念?
      可以把znode理解成Redis的一个Key,但是znode之间有层次关系。
    3. 在某些场景,经常用Redis做分布式锁(setnx命令),只是redis没有将请求者进行排队, 与 zookeeper的分布式锁有和区别?
      如果一个调用setnx的Redis客户端crash,它设置的key还会存在,换言之锁不会自动释放。在ZooKeeper里面,我们用临时节点表示锁,如果ZooKeeper客户端crash,它的锁会自动释放;ZooKeeper实现的锁可以在锁释放时只通知一个锁请求者,还保证锁分配的FIFO。Zookeeper的锁方案更加完备。
      另外Redis(https://redis.io/commands/setnx)本身也不推荐使用setnx了。
    4. zookeeper分布式锁为了避免羊群效应,采用的是公平锁。但是公平锁有一个副作用:
      比如节点1获得了锁,节点2客户端watch节点1,节点3客户端watch节点2,如果此时节点2的客户端心跳失败,触发watch机制,节点3的客户端要更换watch节点,也就是会watch锁持有者节点1,否则一旦节点1释放锁后,其他节点客户端就会永远感知不到。
      但是非公平锁,不会存在这个问题。中间其它未持有锁的client端的session失效,并不会对其他客户端产生影响。在锁竞争并不会特别激烈的场景下,非公平锁的性能会更佳。正因为如此,jdk下JUC包里面的锁类默认都采用非公平模式。

    相关文章

      网友评论

          本文标题:Zookeeper学习-08 Zookeeper 分布式锁

          本文链接:https://www.haomeiwen.com/subject/jikcmhtx.html