Zookeeper学习-08 Zookeeper 分布式锁

一、设计

使用临时顺序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 分布式锁)