实现Zookeeper分布式锁*(排他锁和共享锁)

1.分布式锁

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。
如果不同的系统或同一个系统下不同的主机访问同一个或者一组资源。那么访问这些资源的时候,往往需要一些通过一些互斥的手段来防止彼此之间的干扰,以保证一致性,这种时候就需要分布式锁了。

2.排他锁(概念)

排他锁(ExculusiveLocks) 又称为写锁或独占锁。是一种锁类型,如果事务T1对数据对象O加上了排他锁。
那么在整个加锁期间,只有事务T1可以对数据对象O进行读写操作,其它任何事务都不能对数据对象O进行任何类型的操作。
直到T1释放了排他锁。

2.1 zookeeper实现排他锁
从排他锁的概念上看,我们知道排他锁的核心就是如何保证当前有仅只有一个事务获得锁,并且锁被释放后,所有正在等待获取锁的事务都能得到通知
	1.定义锁
	在通常的JAVA开发编程中,有两种常见的方式来获取锁,分别是synchronized机制和JDK5提供的ReentrantLock。
	但在zookeeper中,没有类似这样的API可以使用。而是通过zookeeper上的数据节点来表示一个锁.
	例如 /exculsive_lock/lock,如图

在这里插入图片描述

	2.获取锁
	在需要获取排他锁的时候,所有的客户端都会试图通过调用create()接口,在/exchusive_lock节点下创建临时节点/excliusive_lock/lock。
	但zookeeper会保证在所有的客户端中,最终只有一个客户端能够创建成功,那就可以认为该客户端获取了锁。
	同时,所有没有获取到锁的客户端就需要到/exclusive_lock节点上注册一个子节点变更的watcher监听。
	以便于实时监听到lock节点的变更情况。
	3.释放锁
	/exclusive_lock/lock 是一个临时节点,所以在两种请看下,都有可能释放锁。
	 - 当前获取锁的客户端机器发生宕机,那么Zookeeper上临时节点就会被移除。
	 - 正常执行完业务逻辑后,客户端就会主动将自己创建的临时节点剔除。
	两种请看下,Zookeeper都会通知所有在/exclusive_lock节点上注册了子节点变更Watch监听的客户端。
	接收到通知后,这些客户端会再次重新发起分布式锁获取。即重复"获取锁"过程。整个排他锁的获取和释放流程,如下图

在这里插入图片描述

3.共享锁

共享锁(Shared Locks ) 又称为读锁。是一种锁类型
如果事务T1对数据对象O加上了共享锁,那么当前事务只能对O进行读取操作,其它事务也只能对这个数据对象加共享锁,一直到该数据对象上所有共享锁都被释放。

共享锁和排他锁的根本区别就是:加上排他锁后,数据对象只对一个事务可见,而加上共享锁后,数据对所有事务可见。

3.1 Zookeeper实现共享锁

1.定义锁:
和排他锁一样,同样是Zookeeper一个数据节点来表示一个锁。
是一个类似于"/shared_lock/[Hostname]-请求类型-序号"的临时顺序节点,例如/shared_lock/host1-R-000000000001,那么,这个节点就代表了一个共享锁,如图所示

在这里插入图片描述

2.获取锁
在需要获取共享锁时,所有客户端都会到/shared_lock这个节点下创建一个临时顺序节点。
如果时读请求,那么就创建例如/shared_lock/host1-R-0000000001的节点;
如果是写请求,就创建例如/shared_lock/host1-W-0000000002的节点
通过Zookeeper来确定分布式读写顺序,大致分为四步
    1. 创建完节点后,获取/shared_lock节点下所有⼦节点,并对该节点变更注册监听。
    2. 确定⾃⼰的节点序号在所有⼦节点中的顺序。
    3. 对于读请求:若没有⽐⾃⼰序号⼩的⼦节点或所有⽐⾃⼰序号⼩的⼦节点都是读请求,那么表
明⾃⼰已经成功获取到共享锁,同时开始执⾏读取逻辑,若有写请求,则需要等待。对于写请求:若⾃⼰不
是序号最⼩的⼦节点,那么需要等待。
    4. 接收到Watcher通知后,重复步骤1
3.释放锁
共享锁的释放流程和独占锁一样

**

4.羊群效应

**

上面讲解的这个共享锁实现,大体上能够满足一般的分布式集群竞争锁的需求,并且性能都还可以——这里说的一般场景是指集群规模不是特别大,一般是在10台机器以内。

但是如果机器规模扩大之后,会有什么问题呢?我们着重来看上面"判断读写顺序"过程的步骤3,结合下面的图,看看实际运行中的情况

在这里插入图片描述

针对如上图所示的情况进行分析

1.host1首先进行读操作,完成后将节点/shared_lock/host1-R-0000001删除。


2.余下4台机器均收到这个节点移除的通知,然后重新从/shared lock节点上获取一份新的子节点列表。



3.每台机器判断自己的读写顺序,其中host2检测到自己序号最小,于是进行写操作,余
下的机器则继续等待。


4.继续可以看到,host1客户端在移除自己的共享锁后,Zookeeper发送了子节点更变Watcher通知给所有机器,然而除了给host2产生影响外,对其他机器没有任何作用。

大量的Watcher通知和子节点列表获取两个操作会重复运行,这样不仅会对zookeeper服务器造成巨大的性能影响影响和网络开销。

更为严重的是,如果同一时间有多个节点对应的客户端完成事务或是事务中断引起节点消失,ZooKeeper服务器就会在短时间内向其余客户端发送大量的事件通知,这就是所谓的**羊群效应**'上面这个ZooKeeper分布式共享锁实现中出现羊群效应的根源在于,没有找准客户端真正的关注点'。

我们再来回顾一下上面的分布式锁竞争过程,它的核心逻辑在于∶'判断自己是否是所有子节点中序号最小的。'

很容易可以联想到,每个节点对应的客户端只需要关注比自己序号小的那个相关节点的变更情况就可以了。
而不需要关注全局的子列表变更情况。可以有如下改动来避免羊群效应。

改进后的分布式锁实现∶

首先,我们需要肯定的一点是,上面提到的共享锁实现,从整体思路上来说完全正确。这里主要的改动在于∶ 每个锁竞争者,只需要关注/shared_lock节点下序号比自己小的那个节点是否存在即可,

具体实现如下。

1.客户端调用create接口常见类似于/shared lock/【Hostname】-请求类型-序号的临时顺序节点。 



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



3.如果无法获取共享锁,就调用exist接口来对比自己小的节点注册Watcher。
 - 对于读请求∶向比自己序号小的最后一个写请求节点注册Watcher监听。 
 - 对于写请求∶ 向比自己序号小的最后一个节点注册Watcher监听。





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



此方案改动主要在于∶每个锁竞争者,只需要关注/shared_lock节点下序号比自己小的那个节点是否存在即可。

在这里插入图片描述

注意 相信很多人都会觉得改进后的分布式锁实现相对来说比较麻烦。
如同在多线程并发编程实践中,我们会去尽量缩小锁的范围。对于分布式锁实现的改进其实也是同样的思路。
那么对于开发人员来说,是否必须按照改进后的思路来设计实现自己的分布式锁呢?
'答案是否定的。'

'在具体的实际开发过程中,我们提倡根据具体的业务场景和集群规模来选择适合自己的分布式锁实现∶'
1.在集群规模不大、网络资源丰富的情况下,第一种分布式锁实现方式是简单实用的选择;
2.如果集群规模达到一定程度,并且希望能够精细化地控制分布式锁机制,那么就可以试试改进版的分布式锁实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酆都小菜鬼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值