Zookeeper实现分布式锁

Zookeeper分布式锁
1、分布式锁
分布式锁在一组进程之间提供互斥机制,任何时刻只有一个进程可以持有锁,持有锁的进程就是系统的“领导者”。分布式锁分为两种:独占锁和共享锁:
独占锁:所有尝试获取锁的客户端,最终只有一个可以成功获得锁;
共享锁:所有尝试获取锁的客户度,最终都会被执行,只是有个全局时序。
2、Zookeeper实现独占锁
2.1、基本思路
利用Znode名称的唯一性,加锁操作时,所有客户端一起创建/xxx/lock节点,只有一个创建成功,成功者获得锁。
2.1.1、存在的问题
2.1.1.1、连接丢失问题
(1)问题
此独占锁算法存在一个问题:不能处理因连接丢失而导致的Create操作失败的问题。由于无法知道创建临时Znode的操作成功还是失败,而且创建操作不是一个幂等操作(多次请求结果一致,并且不会产生副作用),所以,如果第一次创建成功,重试会使得出现一个永远删不掉的孤儿节点(至少在客户端会话结束前),从而导致出现死锁。
(2)解决方案
问题在于:重新连接后,客户端无法判断Znode节点是否是自己创建的。
解决方案:在创建Znode节点时,将客户端的身份ID写入到Znode数据区。客户端的会话ID是一个长整数,并且在Zookeekper服务中是唯一的,可以在连接丢失后用于识别客户端。因此,在创建时,将客户端的会话ID作为身份标识,写入Znode中。在加锁的时候,如果Znode节点已存在,则读取Znode中保存的身份ID,与自身的会话ID比较,如果一致就表示上一次创建成功,直接加锁成功。
2.1.1.2、加锁时间问题
(1) 问题
如果获得锁的客户端加锁时间足够短,可能会存在加锁失败的客户端调用接口zoo_wexists设置Watcher失败,从而导致客户端加锁失败。
(2)解决方案
在设置Watcher失败时,判断失败原因是否是ZNONODE,如果是,则进入重新创建Znode流程,无需进入等待流程。
2.1.1.3、无法处理问题
由于加锁的客户端需要直接对锁节点设置Watch监控节点的创建/删除,所以,如果成百上千的客户端同时加锁,那么,当获得锁的客户端释放锁时,Zookeeper服务需要同时向成百上千的客户端发送Watch事件,会对Zookeeper服务产生压力。
2.3、Zookeeper实现独占锁方案
基于上述思路,实现独占锁的主要流程如下图所示。
在这里插入图片描述
图1 Zookeeper实现独占锁流程图

3、Zookeeper实现共享锁
3.1、基本思路
3.1.1、概述
Zookeeper中的顺序节点,即:在/lock/目录下创建3个节点,Zookeeper会按照提起创建的顺序来创建节点,节点分别为/lock/0000000001、/lock/0000000002和/lock/0000000003。
Zookeeper中的临时节点在客户端与Zookeeper集群断开连接后会自动被删除。
因此,我们可以利用Zookeeper的临时、顺序节点来实现共享锁,思路如下:
① 首先指定一个作为锁的Znode,称为/lock;
② 然后希望获取锁的客户端在/lock目录下创建临时的顺序Znode,作为锁Znode的子节点;
③ 任何时刻,顺序号最小的客户端持有锁;
例如:有两个客户端同时创建znode,分别是/lock/lock-1和/lock/lock-2,那么创建/lock/lock-1的客户端将持有锁。Zookeeper服务是顺序的仲裁者,负责分配顺序号。

④ 删除Znode(/lock/lock-1)即可释放锁;
⑤ 如果客户端进程死亡,则对应的Znode会自动被删除;
⑥ 接下来创建/lock/lock-2的客户端持有锁,通过创建znode删除的观察,可以使客户端在获得锁的时候得到通知。
3.1.2、存在的问题
3.1.2.1、羊群效应
(1)问题
如果有成百上千的客户端在尝试获取锁,每个客户端都会在锁Znode上设置一个Watcher用于捕捉子节点的变化。每次锁释放或另一个进程开始申请获取锁的时候,Watcher都会被触发,并且每个客户度都会收到一个通知。“羊群效应”是指大量客户端收到同一个事件通知,但是实际上只有部分需要处理此事件。在这种情况下,只有一个客户端会成功获取锁,但向客户端发送Watch事件会产生峰值流量,对Zookeeper服务造成压力。
(2)解决方案
为了避免出现羊群效应,需要优化Watcher触发的条件。
在此方案中,只有前一个顺序号的子节点被删除时才需要通知下一个客户端获得锁,而不是删除(或创建)任何子节点时都需要通知。比如:
如果客户端创建了Znode:/lock/lock-1、/lock/lock-2和/lock/lock-3,那么只有当/lock/lock-2被删除时才需要通知创建/lock/lock-3的客户端,而/lock/lock-1被删除或新的节点/lock/lock-4被创建时,都不需要通知该客户端。
3.1.2.2、连接丢失问题
(1)问题
此加锁算法存在一个问题:不能处理因连接丢失而导致的Create操作失败的问题。由于无法知道创建顺序Znode的操作成功还是失败,而且创建操作不是一个幂等操作(多次请求结果一致,并且不会产生副作用),所以,如果第一次创建成功,重试会使得出现一个永远删不掉的孤儿节点(至少在客户端会话结束前),从而导致出现死锁。
(2)解决方案
问题在于:在重新连接之后,客户端不能判断它是否已经创建过子节点。
解决方案:在Znode的名称中嵌入一个ID,如果客户端出现连接丢失的情况,重新连接之后,先检查锁节点的所有子节点,看是否有子节点的名称中包含其ID。如果一个子节点的名称包含其ID,则表示上一次创建操作成功,不需要再创建子节点;如果没有子节点的名称中包含其ID,则客户端需要创建一个新的顺序子节点。
客户端的会话ID是一个长整数,并且在Zookeekper服务中是唯一的,可以在连接丢失后用于识别客户端。可以调用Zookeekper C API中的zoo_client_id()获取。
因此,在创建临时的顺序Znode时,应当采用“lock--”这样的命名方式,Zookeeper在其尾部添加顺序号之后,Znode的名称形式变成:lock--。由于顺序号对于父节点来说是唯一的,因此,采用此命名方式可以使得子节点在保持创建顺序的同时可以确认自己的创建者。
3.1.2.3、不可恢复异常
如果客户端与Zookeeper服务之间的会话过期,则它创建的Znode将会被删除,持有的锁被释放。由于锁无法区分此时是正常的锁释放还是异常导致的锁释放,所以,对于此场景的锁释放,应用程序需要自己清理自身的状态,然后重新尝试申请新的锁。

3.2、Zookeeper实现共享锁方案
对于加锁操作,所有客户端都在/lock目录下创建临时的顺序节点,如果创建的客户端发现自身创建节点序列号是/lock/目录下最小的节点,则获得锁;否则,监视比自己创建节点的序列号小的节点,进入等待。
解锁操作就是将自身创建的节点删除即可。
具体算法流程如下图所示。
在这里插入图片描述
图2 Zookeeper实现共享锁流程图

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值