本文基于ZooKeeper Curator进行分布式锁的实践
搭建ZooKeeper环境
这里基于Docker搭建ZooKeeper环境
1 | # 拉取 ZooKeeper 镜像 |
POM依赖
Curator,作为Netflix开源的ZooKeeper客户端框架,大大简化了我们操作、使用ZooKeeper的难度,并且提供了非常丰富的基于链式调用的API。故这里首先在POM中引入Curator依赖,其中我们需要在Curator依赖中排除ZooKeeper依赖,然后单独引入与服务端版本一致的ZooKeeper依赖
1 | <dependencies> |
Curator基本实践
创建节点
众所周知,在ZooKeeper中节点支持两种类型:持久/临时、有序/无序。即两两组合则共计四种节点。其中临时节点会在客户端连接断开后自动被删除,而持久节点则不会;有序节点在创建过程中则会被分配一个唯一的单调递增的序号,并将序号追加在节点名称中,而无序节点则不会。下面即是一个基于Curator创建节点的示例
1 | /** |
测试结果如下所示,符合预期
Watcher机制
ZooKeeper通过引入Watched机制实现发布/订阅功能,但原生的Watcher机制一旦触发一次后就会失效。如果期望一直监听,则必须每次重复注册Watcher,使用起来较为繁琐。为此Curator对其进行了优化,实现了自动注册,以便进行重复监听。具体地,Curator中提供了三种监听器:NodeCache、PathChildrenCache、TreeCache。其中,NodeCache只可监听指定路径所在节点的创建、修改、删除;PathChildrenCache只可监听指定路径下的第一级子节点的创建、修改、删除,无法监听指定路径所在节点的事件,无法监听指定路径的子节点的子节点的事件;TreeCache可监听指定路径所在节点的创建、修改、删除,可监听指定路径下的所有各级子节点的创建、修改、删除
NodeCache实践
下面即是一个基于Curator实践NodeCache的示例
1 | /** |
测试结果如下所示,符合预期
PathChildrenCache实践
下面即是一个基于Curator实践PathChildrenCache的示例
1 | /** |
测试结果如下所示,符合预期
TreeCache实践
下面即是一个基于Curator实践TreeCache的示例
1 | /** |
测试结果如下所示,符合预期
分布式锁
Curator还进一步地提供了非常丰富的分布式锁特性,具体包括:
- InterProcessMutex 分布式可重入互斥锁
- InterProcessReadWriteLock 分布式可重入读写锁
- InterProcessSemaphoreMutex 分布式不可重入互斥锁
- InterProcessSemaphoreV2 分布式信号量
InterProcessMutex分布式可重入互斥锁
InterProcessMutex是一个分布式的可重入的互斥锁,示例代码如下所示
1 | /** |
测试结果如下所示,符合预期
InterProcessReadWriteLock分布式可重入读写锁
读写互斥
InterProcessReadWriteLock是一个分布式可重入读写锁,其中读锁为共享锁、写锁为互斥锁。示例代码如下所示
1 | /** |
测试结果如下所示,符合预期
可重入性
由于读锁、写锁分别是基于InterProcessMutex实现的,故这二者自然也是支持可重入的。示例代码如下所示
1 | /** |
测试结果如下所示,符合预期
锁升级、锁降级
所谓锁升级指的是读锁升级为写锁。当一个线程先获取到读锁再去申请写锁,显然其是不支持的。理由也很简单,读锁是可以多个服务实例同时持有的。若其中一个服务实例此锁线程能够进行锁升级,成功获得写锁。显然与我们之前的所说的读写互斥相违背。因为其在获得写锁的同时,其他服务实例依然持有读锁;反之,其是支持锁降级的,即写锁降级为读锁。当一个服务实例的线程在获得写锁后,该线程依然可以获得读锁。这个时候当其释放写锁,则将只持有读锁,即完成了锁降级过程。锁降级的使用价值也很大,其一方面保证了安全,读锁在写锁释放前获取;另一方面保证了高效,因为读锁是共享的。
锁升级示例代码如下所示
1 | /** |
测试结果如下所示,在持有读锁的情况下,继续尝试获取写锁会被一直阻塞
锁降级示例代码如下所示
1 | /** |
测试结果如下所示,符合预期
InterProcessSemaphoreMutex分布式不可重入互斥锁
InterProcessSemaphoreMutex则是一个分布式不可重入互斥锁,示例代码如下所示
1 | /** |
测试结果如下所示,符合预期。Test 1结果证明其是一个互斥锁,而Test 2则在第二次获取锁时被阻塞,证明其不可重入
InterProcessSemaphoreV2分布式信号量
InterProcessSemaphoreV2是一个的分布式信号量,示例代码如下所示
1 | /** |
测试结果如下所示,符合预期。其每次同时处理的用户请求数最大只有2个