Zookeeper分布式锁实现Curator十一问(一)

pivoteic
发布于 2022-6-15 17:07
浏览
0收藏

 

Zookeeper分布式锁实现Curator十一问(一)-鸿蒙开发者社区

前面我们通过Redis分布式锁实现Redisson 15问文章剖析了Redisson的源码,理清了Redisson是如何实现的分布式锁和一些其它的特性。这篇文章就来接着剖析Zookeeper分布式锁的实现框架Curator的源码,看看Curator是如何实现Zookeeper分布式锁的,以及它提供的哪些其它的特性。

 

Curator框架是封装对于zk操作的api,其中就包括了对分布式锁的实现,当然Curator框架也包括其它的功能,分布式锁只是Curator的一部分功能。

 

本文的目录跟Redisson的目录比较相似,主要是为了方便大家对比redis和zk分布式锁的实现。

 

一、ZK分布式锁实现原理

 

实现Zookeeper分布式锁,主要是基于Zookeeper的临时顺序节点来实现的。

 

当客户端来加锁的时候,会先在加锁的节点下建立一个子节点,这个节点就有一个序号,类似 lock-000001 ,创建成功之后会返回给客户端所创建的节点,然后客户端会去获取这个加锁节点下的所有客户端创建的子节点,当然也包括自己创建的子节点。拿到所有节点之后,给这些节点进行排序,然后判断自己创建的节点在这些节点中是否排在第一位,如果是的话,那么就代表当前客户端就算加锁成功了,如果不是的话,那么就代表当前客户端加锁失败。

 

加锁失败的节点并不会不停地循环去尝试加锁,而是在自己创建节点的前一个节点上加一个监听器,然后就进行等待。当前面一个节点释放了锁,就会反过来通知等待的客户端,然后客户端就加锁成功了。

 

为什么需要在前一个节点加个监听器?

 

假设有很多客户端来加锁,然后加锁失败的都对前一个节点加一个监听。那么一旦第一个加锁成功的客户端线程释放了锁,那么被唤醒的就是第二个客户端线程,第二个客户端线程就会加锁成功,执行完任务之后就释放了锁,那么就会唤醒第三个客户端线程,第三个客户端线程加锁成功,执行完任务之后就释放了锁,唤醒第四个客户端线程,以此类推,所以每次释放锁都会唤醒下一个节点,这样每个加锁的线程都会加锁成功,所以监听器的作用是唤醒加锁失败阻塞等待的客户端。

 

二、为什么使用临时顺序节点

 

下面介绍一下临时节点、持久化节点、顺序节点的特性。

 

1)临时节点

临时节点,指的是节点创建后,如果创建节点的客户端和 Zookeeper 服务端的会话失效(例如断开连接),那么节点就会被删除。

2)持久化节点

持久化节点指的是节点创建后,即使创建节点的客户端和 Zookeeper 服务端的会话失效(例如断开连接),节点也不会被删除,只有客户端主动发起删除节点的请求,节点才会被删除。

3)有序节点


有序节点,这种节点在创建时会有一个序号,这个序号是自增的。有序节点既可以是有序临时节点,也可以是有序持久化节点。

从上面节点的特性可以知道,临时节点相比持久节点,最主要的是对会话失效的情况处理不一样,如果使用临时节点的话,如果客户端发生异常的话,没有来得及主动释放锁,就能避免锁无法释放导致死锁的情况。因为一旦客户端异常,那么客户端和服务端之间的会话就会失效,然后临时节点就会被删除,这样就释放了锁;而持久化节点在由于会话失效无法被删除,那么就不会去释放锁,这样就会产生死锁的问题。

从这里可以看出redis和zk防止死锁的实现是不同的,redis是通过过期时间来防止死锁,而zk是通过临时节点来防止死锁的。


为什么使用顺序节点?其实为了防止羊群效应。如果没有使用顺序节点,假设很多客户端都会去加锁,那么加锁就会都失败,都会对加锁的节点加个监听器,那么一旦锁释放,那么所有的加锁客户端都会被唤醒来加锁,那么一瞬间就会造成很多加锁的请求,增加服务端的压力。


所以综上,临时顺序节点是个比较好的选择。

 

三、加锁的逻辑是如何实现的

 

前面关于ZK分布式锁实现原理已经说过了,接下来就来看一下代码的实现。

加锁的使用方法如下,接下来几节会着重讲解这段代码背后的逻辑

Zookeeper分布式锁实现Curator十一问(一)-鸿蒙开发者社区acquire方法的实现

Zookeeper分布式锁实现Curator十一问(一)-鸿蒙开发者社区acquire方法会去调用internalLock方法,传入超时时间 -1 和单位 null,也就代表了如果加锁不成功会一直阻塞直至加锁成功,不会超时。

Zookeeper分布式锁实现Curator十一问(一)-鸿蒙开发者社区

internalLock方法会先去获取当前线程,然后从threadData中获取当前线程对应的LockData,这里面封装了加锁的信息和次数,是实现可重入锁的关键,当然第一次加锁这里肯定是没有的,会继续下走 internals.attemptLock 加锁。

 

文章转自公众号:三友的java日记

标签
已于2022-6-15 17:07:23修改
收藏
回复
举报
回复
    相关推荐