zookeeper分布式锁有哪些?有什么意义?

2020-04-27 14:46:00 java常见问答 3261

今天给大家带来的知识则是zookeeper,不知道大家是否了解呢?不了解的没有关系,今天就跟着小编的步伐一起来了解下吧。

ZooKeeper提供分布式协调服务,应用非常广泛。然而分布式锁分为两类排他锁以及共享锁。

1首先来介绍下什么是排他锁?

又叫写锁或者独占锁,是一种基本的锁类型。若事务T对数据对象O加上了排他锁,在加锁的这个期间,只允许事务T对O进行读取和更新的操作,直到T释放锁,其他事务才能对这个数据的对象进行操作。

然而我们应该如何使用排他锁呢?

1)在Java开发中,有两种常见的方式来定义锁,分别是synchronized和ReentrantLock。ZooKeeper通过其上的一个数据节点表示一个锁,例如创建的临时节点/exclusive/lock就可以表示一个锁。

2)在获取排他锁时,所有客户端节点都会调用create()接口,在/exclusive下创建节点/exclusive/lock。ZK保证在所有客户端中,只有一个客户端能够创建成功,就可以认为该客户端获取到锁。同时,所有没有获取到锁的客户端需要在/exclusive/lock节点注册一个Watcher监听,以便实时监听lock节点的变更情况。

3)在下面的两种情况下,可能会释放锁。

(1)当前获取锁的客户端机器发生宕机,zookeeper上的临时节点会被移除。

(2)正常执行完业务逻辑后,客户端会主动删除创建的临时节点。

无论在哪种情况下移除了lock节点,zookeeper都会通知所有注册了Watcher监听的客户端。这些客户端在接收到通知后,重新发起分布式锁获取,即重复“获取锁”过程。

如何优化排他锁?

对于上述的场景,当大量客户端去竞争锁的时候,会发生“惊群”效应。这里惊群效应指的是在分布式锁竞争的过程中,大量的"Watcher通知"和“创建/exclusive/lock”两个操作重复运行,并且绝大多数运行结果都创建节点失败,从而继续等待下一次通知。若在集群规模较大的情况下,会对ZooKeeper服务器以及客户端服务器造成巨大的性能影响和网络冲击。

解决方案:

1)客户端调用create()方法创建名为“/exclusive/lock-”节点,这里节点类型创建类型设置为EPHEMERAL_SEQUENTIAL;

2)客户端调用getChildren(“/exclusive”)方法来获取所有已经创建的子节点,若发现自身的节点序号是/exclusive目录下最小的点,则获得锁;否则,监视比自己创建的节点的序列号小的最大节点,进入等待。

3)这样就可以避免了"惊群效应",多个客户端共同等待锁,锁释放时只有一个客户端会被唤醒。

2. 共享锁是什么?

又叫为读锁,若事务T1对数据对象O1加上了共享锁,当前事务只能对O1进行读取操作,其他事务只能对这个数据对象加共享锁---直到该数据对象上的所有共享锁都被释放。

共享锁与排他锁的根本区别在于:加上排他锁之后,数据对象只对一个事务可见,而加上共享锁后,数据对所有事务都可见。

如何使用共享锁呢?

1)同样使用ZK上的数据节点表示一个锁,是一个类似于“/shared_lock/[Hostname]-请求类型-序号”的临时顺序节点,例如/shared_lock/192.168.0.1-R-0000000001就代表了共享锁。

2)在获取共享锁时,所有客户端都会到/shared_lock节点下创建一个临时顺序节点,若是读请求,则创建/shared_lock/192.168.0.1-R-0000000001;若是写请求,则创建/shared_lock/192.168.0.1-W-0000000001。

3)根据共享锁的定义,不同的事务都可以同时对同一个数据对象进行读取操作,而更新操作必须在当前没有任何事务进行读写操作的情况下进行。基于这个原则,我们来看看如何通过ZK的节点来确定分布式读写顺序,大致可以分为4个步骤:

(1) 创建完节点后,获取/shared_lock节点下的所有子节点,并对该节点注册子节点变更的Watcher监听。

(2) 确定自己的节点序号在所有子节点中的顺序。

(3) 对于读请求:若没有比自己序号小的子节点,或是所有比自己序号小的请求都是读请求,那么表明自己已经成功获取到了共享锁,同时开始执行读取逻辑;若比自己序号小的子节点中有写请求,那么需要进入等待。

对于写请求:若自己是序号最小的子节点,则执行读取逻辑;否则进入等待。

(4) 接收到Watcher通知后,重复步骤(1)。

如何优化共享锁?

对于上述的场景,当大量客户端去竞争锁的时候,会发生“惊群”效应。这里惊群效应指的是在分布式锁竞争的过程中,大量的"Watcher通知"和“子节点列表获取”两个操作重复运行,并且绝大多数运行结果都判断自己并非是序号最小的节点,从而继续等待下一次通知。若在集群规模较大的情况下,会对ZooKeeper服务器以及客户端服务器造成巨大的性能影响和网络冲击。

解决方案:上面提到的共享锁实现,从整体思路上来说完全正确。这里的主要改动在于:每个锁竞争者只需要关注shared_lock节点下序号比自己小的那个节点是否存在即可,具体改进如下所示:

1)客户端调用create()方法创建一个类似于“shared_lock/[Hostname]-请求类型-序号”的临时顺序节点。

2)客户端调用getChildren()接口来获取所有已经创建的子节点列表,注意,这里不注册任何Watcher。

3)若无法获取共享锁,调用exist()对比自己小的节点注册Watcher。

读请求:向比自己序号小的最后一个写请求节点注册Watcher监听。

写请求:向比自己序号小的最后一个节点注册Watcher监听。

4)等待Watcher通知,继续进入步骤2。

好了,今天的zookeeper分布式缩的相关内容就讲到这里了,如果想要了解更多java一些知识问答,那么就请继续关注本网站吧。