curator 本身提供了非常多的功能点,比如分布式锁,leader选举,类似java的CyclicBarrier,在这里不再列举了
这里主要讲解curator的分布式的锁的基本实现以及源码分析
1.curator的分布式锁实现
/**
* @Project: 3.DistributedProject
* @description: curator的分布式锁的实现
* @author: sunkang
* @create: 2018-06-24 10:09
* @ModificationHistory who when What
**/
public class CuratorLocks {
public static void main(String[] args) throws Exception {
CuratorFramework curatorFramework = CuratorFrameworkFactory.builder().connectString("192.168.44.129:2181")
.sessionTimeoutMs(4000).retryPolicy(new ExponentialBackoffRetry(4000,3))
.build();
curatorFramework.start();
//InterProcessMutex这个锁为可重入锁
InterProcessMutex interProcessMutex = new InterProcessMutex(curatorFramework,"/locks");
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
fixedThreadPool.submit(new Runnable() {
@Override
public void run() {
boolean flag = false;
try {
//尝试获取锁,最多等待5秒
flag = interProcessMutex.acquire(5, TimeUnit.SECONDS);
Thread currentThread = Thread.currentThread();
if(flag){
System.out.println("线程"+currentThread.getId()+"获取锁成功");
}else{
System.out.println("线程"+currentThread.getId()+"获取锁失败");
}
//模拟业务逻辑,延时4秒
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
} finally{
if(flag){
try {
//释放锁
interProcessMutex.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
});
}
}
}
输出结果如下: 后面的两个,因为超时了,导致锁没有获取到
线程18获取锁成功
线程16获取锁成功
线程19获取锁成功
线程20获取锁失败
线程17获取锁失败
2.curator的分布式锁源码分析
可以先看这句话:
InterProcessMutex interProcessMutex = new InterProcessMutex(curatorFramework,"/locks");
最终进入到这个构造方法,主要进行了一些初始化变量的初始化,maxLeases的在后面的源码可以看到是监听比自己小一个的节点
InterProcessMutex(CuratorFramework client, String path, String lockName, int maxLeases, LockInternalsDriver driver) {
this.threadData = Maps.newConcurrentMap();
this.basePath = PathUtils.validatePath(path);
//path为/locks ,lockName 为“lock-”,maxLeases = -1,driver 为StandardLockInternalsDriver
this.internals = new LockInternals(client, driver, path, lockName, maxLeases);
}
然后看上面例子的代码写的获取锁的这句话,基于这句话,完成后面的分析
flag = interProcessMutex.acquire(5, TimeUnit.SECONDS);
实际到了调用了internalLock的方法
private boolean internalLock(long time, TimeUnit unit) throws Exception {
Thread currentThread = Thread.currentThread();
//获得当前线程的锁
InterProcessMutex.LockData lockData = (InterProcessMutex.LockData)this.threadData.get(currentThread);
//如果锁不为空,当前线程已经获得锁,可重入锁,lockCount++
if (lockData != null) {
//主要做一个可重入锁,锁的调用次数增加1
lockData.lockCount.incrementAndGet();
return true;
} else {
//获取锁,返回锁的节点路径
String lockPath = this.internals.attemptLock(time, unit, this.getLockNodeBytes());
if (lockPath != null) {
//向当前锁的map集合添加一个记录
InterProcessMutex.LockData newLockData = new InterProcessMutex.LockData(currentThread, lockPath);
this.threadData.put(currentThread, newLockData);
return true;
} else {
return false;//获取锁失败
}
}
}
下面是threadData的数据结构,是一个Map结构,key是当前线程,value是当前线程和锁的节点的一个封装对象。
private final ConcurrentMap<Thread, InterProcessMutex.LockData> threadData;
private static class LockData {
final Thread owningThread;
final String lockPath;
//锁的调用次数,可以重入,每重入一次,增加1
final AtomicInteger lockCount;
private LockData(Thread owningThread, String lockPath) {
this.lockCount = new AtomicInteger(1);
this.owningThread = owningThread;
this.lockPath = lockPath;
}
由internalLock方法可看到,最重要的方法是attemptLock方法
String lockPath = this.internals.attemptLock(time, unit, this.getLockNodeBytes());
String attemptLock(long time, TimeUnit unit, byte[] lockNodeBytes) throws Exception {
long startMillis = System.currentTimeMillis();
//将等待时间转化为毫秒
Long millisToWait = unit != null ? unit.toMillis(time) : null;
byte[] localLockNodeBytes = this.revocable.get() != null ? new byte[0] : lockNodeBytes;
//重试次数
int retryCount = 0;
String ourPath = null;
boolean hasTheLock = false;
boolean isDone = false;
while(!isDone) {
isDone = true;
try {
//在当前path下创建临时有序节点
ourPath = this.driver.createsTheLock(this.client, this.path, localLockNodeBytes);
//判断是不是序号最小的节点,如果是返回true,否则阻塞等待
hasTheLock = this.internalLockLoop(startMillis, millisToWait, ourPath);
} catch (NoNodeException var14) {
if (!this.client.getZookeeperClient().getRetryPolicy().allowRetry(retryCount++, System.currentTimeMillis() - startMillis, RetryLoop.getDefaultRetrySleeper())) {
throw var14;
}
isDone = false;
}
}
//返回当前锁的节点路径
return hasTheLock ? ourPath : null;
}
下面来看internalLockLoop方法,判断是不是最小节点的方法
private boolean internalLockLoop(long startMillis, Long millisToWait, String ourPath) throws Exception {
boolean haveTheLock = false;
boolean doDelete = false;
try {
if (this.revocable.get() != null) {
((BackgroundPathable)this.client.getData().usingWatcher(this.revocableWatcher)).forPath(ourPath);
}
//自旋
while(this.client.getState() == CuratorFrameworkState.STARTED && !haveTheLock) {
//获取排序好的子节点,这里利用了Collections的Comparator的排序,感兴趣的可以往下看
List<String> children = this.getSortedChildren();
//得到临时节点的字符串
String sequenceNodeName = ourPath.substring(this.basePath.length() + 1);
//这里的driver是启动的时候设置的,具体实现为StandardLockInternalsDriver
//判断是否可以获取锁,并设置监控节点到PredicateResults中
PredicateResults predicateResults = this.driver.getsTheLock(this.client, children, sequenceNodeName, this.maxLeases);
//判断是否是最小节点
if (predicateResults.getsTheLock()) {
haveTheLock = true;
} else {
//给比自己小的节点设置监听器
String previousSequencePath = this.basePath + "/" + predicateResults.getPathToWatch();
//同步,是为了实现公平锁
synchronized(this) {
try {
//注册监听
((BackgroundPathable)this.client.getData().usingWatcher(this.watcher)).forPath(previousSequencePath);
//如果等待时间==null,一直阻塞等待
if (millisToWait == null) {
this.wait();
} else {
millisToWait = millisToWait - (System.currentTimeMillis() - startMillis);
startMillis = System.currentTimeMillis();
//等待超时时间
if (millisToWait > 0L) {
this.wait(millisToWait);
} else {
doDelete = true;//如果超时则删除锁
break;
}
}
} catch (NoNodeException var19) {
;
}
}
}
}
} catch (Exception var21) {
ThreadUtils.checkInterrupted(var21);
doDelete = true;
throw var21;
} finally {
if (doDelete) {
//如果锁超时,删除锁
this.deleteOurPath(ourPath);
}
}
return haveTheLock;
}
接着从StandardLockInternalsDriver的getsTheLock方法里面看,记得初始化的时候,maxLeases=1,children为有序的创建的临时节点,
public PredicateResults getsTheLock(CuratorFramework client, List<String> children, String sequenceNodeName, int maxLeases) throws Exception {
//得到当前节点的位置
int ourIndex = children.indexOf(sequenceNodeName);
validateOurIndex(sequenceNodeName, ourIndex);
//当前节点是否<1,小于1说明为0,getsTheLock 为true,说明可以获得 锁
boolean getsTheLock = ourIndex < maxLeases;
//getsTheLock 为false,pathToWatch 为当前节点的上个节点,即监控节点
String pathToWatch = getsTheLock ? null : (String)children.get(ourIndex - maxLeases);
return new PredicateResults(pathToWatch, getsTheLock);
}
还有一个细节可以看到
//给当前节点的上个节点设置监控 ((BackgroundPathable)this.client.getData().usingWatcher(this.watcher)).forPath(previousSequencePath);
看下watcher的方法
private final Watcher watcher = new Watcher() {
public void process(WatchedEvent event) {
LockInternals.this.notifyFromWatcher();
}
};
//唤醒等待的线程,也就是当节点发生变化的时候,会触发该事件,也就唤醒等待的线程
private synchronized void notifyFromWatcher() {
this.notifyAll();
}
acquire获取锁的细节就分析到这里的,接下里来看release释放锁的逻辑
public void release() throws Exception {
Thread currentThread = Thread.currentThread();
//根据当前线程,获取缓存的数据
InterProcessMutex.LockData lockData = (InterProcessMutex.LockData)this.threadData.get(currentThread);
//当前线程没有获取锁,不能释放
if (lockData == null) {
throw new IllegalMonitorStateException("You do not own the lock: " + this.basePath);
} else {
//锁释放,可重入锁的lockCount的数字减少1,lockCount的初始值为1
int newLockCount = lockData.lockCount.decrementAndGet();
if (newLockCount <= 0) {
if (newLockCount < 0) {
throw new IllegalMonitorStateException("Lock count has gone negative for lock: " + this.basePath);
} else {//只有newLockCount = 0才做这个逻辑,说明可重入锁全部释放
//释放锁
try {
//本质是删除节点
this.internals.releaseLock(lockData.lockPath);
} finally {
//移除
this.threadData.remove(currentThread);
}
}
}
}
}
对应的releaseLock的方法,移除watcher和删除节点
final void releaseLock(String lockPath) throws Exception {
//移除watcher
this.client.removeWatchers();
this.revocable.set((Object)null);
//删除节点
this.deleteOurPath(lockPath);
}