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);
}