curator的分佈式鎖實現和源碼分析 Mrsunup

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);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章