由于项目是要部署到多个节点上进行运行的,并且没有使用主备模式,使用的是主主模式,所以当两个节点上不同进程操作同一资源的时候,需要一个分布式锁对资源进行加锁处理。
目前一般主流的方案都是使用redis来实现的,奈何当前项目处理的更多是离线数据而不是实时数据,基于业务考虑当前版本暂时没有把redis引进来,所以只能基于ZK实现一个分布式锁(压力不算太大,ZK还是抗的住的)。
本来是打算参考github上他人的实现自己写一个的,发下Curator中就有现成的,那就先直接用现成的好了。
//todo 插入使用的demo
可以看出来,Curator已经封装的特别好了…
InterProcessMutex设计思想:
InterProcessMutex是Curator实现的一种分布式可重入排他锁。该锁实现的思想大致如下:
所有的线程都去同一个ZK路径下创建临时有序类型节点(EPHEMERAL_SEQUENTIAL,序号是ZK根据当前子节点数量自动添加整数序号)。
如果线程检测到自身的节点序号最小,表明当前线程获取到了锁。其他节点会在序号比自己小的前一个节点上注册一个Wacth,节点被删除时能够接收到通知,如果发现自己当前的序号是该路径下最小的,表示自己获取到的锁,循环一直不停的执行该流程。
有两种情况会释放锁,第一种是正常情况下事务处理结束释放锁,第二种是ZK检测到客户端连接超时了,主动将节点给删除了。无论上述哪种情况,客户端都不应当再持有锁了。
(PS: 为什么不是往最小的节点注册而是往比自己小的前一个节点上注册,这样做是为了避免“羊群效应”,如果有1000个线程都往最小的那个节点上注册Watch,那么锁被释放时,就会触发往1000个线程发送通知,对ZK集群会产生巨大的性能影响和网络冲击。由此也可以看出,这是一把公平锁)
InterProcessMutex内部具体实现:
获取锁:
内部调用internalLock()方法获取锁,如果获取锁期间与ZK服务端之间的连接发生异常,会抛出异常:
-1和null表示获取锁期间会一直阻塞,当然也可以设置超时时间
看一下internalLock()方法:
内部有锁重入逻辑
正确获取锁之后会在本地线程做一个记录,然后返回true。真正加锁的地方在attemptLock()方法内部:
点到attemptLock()方法内部看下:
createsTheLock()是在ZK路径瞎创建临时节点,注意此时并没有获取到锁,核心其实就是一行代码:
client.create().creatingParentsIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(path, lockNodeBytes);
internalLockLoop()才是加锁,它判断自己的节点序号是不是最小,是则返回true,表示获取到锁,否则阻塞等待
driver.getsTheLock用于判断当前节点的序号是不是最小,返回的是前一个节点的序号(如果不是最小的话),还有是否当前节点是序号最小的标识符(true/false),如下所示:
如果当前节点序号是最小的,那么predicateResults。getTheLock()得到的就是true,表明序号最小,可以持有锁,否则回往欠一个节点上注册WATCH,前一个节点锁释放了可以获取锁。
释放锁:
如果锁不被当前线程持有,调用release()方法会抛出异常,否则将重入次数减一。如果发现减一之后可重入次数已经变成0了,那么删除ZK上的临时节点(表示锁完全被释放,其他线程可以抢占锁了)。
guaranteed()方法可以保证客户端失联的情况下,ZK服务器也能删除临时节点:
几种异常情况说明:
1.服务端手动把节点删除了,客户端还不知道,此时客户端还可以重入么???!!!这种算是异常情况,不是这把锁要考虑的内容。
可以测试一下这种异常情况
这个是线程打印出来的日志,三个连续的“get”是手动删除的结果,说明节点被手动删除这种异常情况不是锁要考虑的
2.客户端和服务端之间的网线被人拔了此时会怎么处理???
心跳超过一定时间,临时节点就会被删除掉了,这样可以保证ZK服务端没有问题,那么客户端呢?!客户端还是认为自己持有锁么?
这个进程中的其他线程肯定不能持有锁了,甚至这台服务器上都不能持有这把锁了。
------ 这种情况是不是只有客户端宕机了才会发生,一般会不会考虑这种异常情况?!
还是说这个版本的代码有缺陷...
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.7.1</version>
</dependency>
参考:
《从Paxos到Zookeeper 分布式一致性原理与实践》
https://www.jianshu.com/p/bc5e24e4f614(Curator客户端创建分析)
https://www.jianshu.com/p/c2b4aa7a12f1(几种分布式锁的实现方式)