前言
Curator是netflix公司开源的一套zookeeper客户端,目前是Apache的顶级项目。与Zookeeper提供的原生客户端相比,Curator的抽象层次更高,简化了Zookeeper客户端的开发量。Curator解决了很多zookeeper客户端非常底层的细节开发工作,包括连接重连、反复注册wathcer和NodeExistsException 异常等。
Curator主要解决了三类问题:
封装ZooKeeper client与ZooKeeper server之间的连接处理
提供了一套Fluent风格的操作API
提供ZooKeeper各种应用场景(recipe, 比如:分布式锁服务、集群领导选举、共享计数器、缓存机制、分布式队列等)的抽象封装,这些实现都遵循了zk的最佳实践,并考虑了各种极端情况
Curator由一系列的模块构成,对于一般开发者而言,常用的是curator-framework和curator-recipes:
curator-framework:提供了常见的zk相关的底层操作
curator-recipes:提供了一些zk的典型使用场景的参考。本节重点关注的分布式锁就是该包提供的
1. 可重入锁InterProcessMutex
Reentrant
和JDK
的ReentrantLock
类似, 意味着同一个客户端在拥有锁的同时,可以多次获取,不会被阻塞。它是由类InterProcessMutex
来实现。
// 常用构造方法
public InterProcessMutex(CuratorFramework client, String path)
// 获取锁
public void acquire();
// 带超时时间的可重入锁
public boolean acquire(long time, TimeUnit unit);
// 释放锁
public void release();
package com.cxb.java;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
/**
* @Classname ReentrantLockZk
* @Date 2023/4/1 14:30
* @Created by Administrator
* @Description TODO
*/
public class ReentrantLockZk {
public static final String ZOOKEEPER_STRING = "127.0.0.1:2181";
CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(ZOOKEEPER_STRING, new ExponentialBackoffRetry(1000, 3));
public ReentrantLockZk() {
curatorFramework.start();
}
/**
* 注意:如想重入,则需要使用同一个InterProcessMutex对象。
*/
public void checkAndLock() {
InterProcessMutex mutex = new InterProcessMutex(curatorFramework, "/curator/lock");
try {
// 加锁
mutex.acquire();
// 处理业务
// 例如查询库存 扣减库存 模拟业务耗时
Thread.sleep(1000);
// 如想重入,则需要使用同一个InterProcessMutex对象
this.testSub(mutex);
// 释放锁
mutex.release();
} catch (Exception e) {
e.printStackTrace();
}
}
public void testSub(InterProcessMutex mutex) {
try {
mutex.acquire();
System.out.println("测试可重入锁。。。。");
mutex.release();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ReentrantLockZk reentrantLockZk = new ReentrantLockZk();
reentrantLockZk.checkAndLock();
}
}
注意:如想重入,则需要使用同一个InterProcessMutex对象。
具体实现:InterProcessSemaphoreMutex
与InterProcessMutex
调用方法类似,区别在于该锁是不可重入的,在同一个线程中不可重入。
public InterProcessSemaphoreMutex(CuratorFramework client, String path);
public void acquire();
public boolean acquire(long time, TimeUnit unit);
public void release();
package com.cxb.java;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
/**
* @Classname NotReentrantSemaphoreMutex
* @Date 2023/4/1 14:42
* @Created by Administrator
* @Description TODO
*/
public class NotReentrantSemaphoreMutex {
public static final String ZOOKEEPER_STRING = "127.0.0.1:2181";
public static CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(ZOOKEEPER_STRING, new ExponentialBackoffRetry(1000, 3));
public NotReentrantSemaphoreMutex() {
curatorFramework.start();
}
public void deduct() {
InterProcessSemaphoreMutex mutex = new InterProcessSemaphoreMutex(curatorFramework, "/curator/lock");
try {
mutex.acquire();
// 处理业务
// 例如查询库存 扣减库存
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
mutex.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 该锁是不可重入锁
* @param args
*/
public static void main(String[] args) {
InterProcessSemaphoreMutex mutex = new InterProcessSemaphoreMutex(curatorFramework, "/curator/lock");
try {
mutex.acquire();
NotReentrantSemaphoreMutex notReentrantSemaphoreMutex = new NotReentrantSemaphoreMutex();
notReentrantSemaphoreMutex.deduct();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
mutex.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
从结果来看,确实不可重入。
3、可重入读写锁InterProcessReadWriteLock
类似JDK
的ReentrantReadWriteLock
。一个拥有写锁的线程可重入读锁,但是读锁却不能进入写锁。这也意味着写锁可以降级成读锁。从读锁升级成写锁是不成的。主要实现类InterProcessReadWriteLock
:
// 构造方法
public InterProcessReadWriteLock(CuratorFramework client, String basePath);
// 获取读锁对象
InterProcessMutex readLock();
// 获取写锁对象
InterProcessMutex writeLock();
注意:写锁在释放之前会一直阻塞请求线程,而读锁不会
package com.cxb.java;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.util.concurrent.TimeUnit;
/**
* @Classname ReadWriteLock
* @Date 2023/4/1 14:49
* @Created by Administrator
* @Description TODO
*/
public class ReadWriteLock {
public static final String ZOOKEEPER_STRING = "127.0.0.1:2181";
public static CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(ZOOKEEPER_STRING, new ExponentialBackoffRetry(1000, 3));
public ReadWriteLock() {
curatorFramework.start();
}
public void testZkReadLock() {
try {
InterProcessReadWriteLock rwlock = new InterProcessReadWriteLock(curatorFramework, "/curator/rwlock");
rwlock.readLock().acquire(10, TimeUnit.SECONDS);
// TODO:一顿读的操作。。。。
Thread.sleep(2000);
System.out.println("正在读");
rwlock.readLock().release();
} catch (Exception e) {
e.printStackTrace();
}
}
public void testZkWriteLock() {
try {
InterProcessReadWriteLock rwlock = new InterProcessReadWriteLock(curatorFramework, "/curator/rwlock");
rwlock.writeLock().acquire(10, TimeUnit.SECONDS);
// TODO:一顿写的操作。。。。
Thread.sleep(2000);
System.out.println("正在写");
rwlock.writeLock().release();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ReadWriteLock readWriteLock = new ReadWriteLock();
readWriteLock.testZkReadLock();
readWriteLock.testZkWriteLock();
}
}
4、联锁InterProcessMultiLock
Multi Shared Lock是一个锁的容器。当调用acquire, 所有的锁都会被acquire,如果请求失败,所有的锁都会被release。同样调用release时所有的锁都被release(失败被忽略)。基本上,它就是组锁的代表,在它上面的请求释放操作都会传递给它包含的所有的锁。实现类InterProcessMultiLock:
// 构造函数需要包含的锁的集合,或者一组ZooKeeper的path
public InterProcessMultiLock(Listlocks);
public InterProcessMultiLock(CuratorFramework client, Listpaths); // 获取锁
public void acquire();
public boolean acquire(long time, TimeUnit unit);// 释放锁
public synchronized void release();
5、信号量InterProcessSemaphoreV2
一个计数的信号量类似JDK的Semaphore。JDK中Semaphore维护的一组许可(permits),而Cubator中称之为租约(Lease)。注意,所有的实例必须使用相同的numberOfLeases值。调用acquire会返回一个租约对象。客户端必须在finally中close这些租约对象,否则这些租约会丢失掉。但是,如果客户端session由于某种原因比如crash丢掉, 那么这些客户端持有的租约会自动close, 这样其它客户端可以继续使用这些租约。主要实现类InterProcessSemaphoreV2:
// 构造方法
public InterProcessSemaphoreV2(CuratorFramework client, String path, int maxLeases);// 注意一次你可以请求多个租约,如果Semaphore当前的租约不够,则请求线程会被阻塞。
// 同时还提供了超时的重载方法
public Lease acquire();
public Collectionacquire(int qty);
public Lease acquire(long time, TimeUnit unit);
public Collectionacquire(int qty, long time, TimeUnit unit) // 租约还可以通过下面的方式返还
public void returnAll(Collectionleases);
public void returnLease(Lease lease);
参考文章