Zookeeper實現分佈式鎖

1.實現步驟

  • 首先要創建一個鎖的根節點,比如/mylock。
  • 想要獲取鎖的客戶端在鎖的根節點下面創建znode,作爲/mylock的子節點,節點的類型要選擇 CreateMode.PERSISTENT_SEQUENTIAL,節點的名字”lock-“,假設目前同時有3個客戶端想要獲得鎖,那麼/mylock下的目錄應該是這個樣子的。
    lock-0000000001,lock-0000000002,lock-0000000003
    “lock-“是前綴 , 0000000001,0000000002,0000000003 是zk服務端自動生成的自增數字。
  • 當前客戶端通過getChildren(/mylock)獲取所有子節點列表並根據自增數字排序,然後判斷一下自己創建的節點的順序是不是在列表當中最小的,如果是 那麼獲取到鎖,如果不是,那麼獲取自己的前一個節點,並設置監聽這個節點的變化,當節點變化時重新執行步驟3 直到自己是編號最小的一個爲止。
    舉例:假設當前客戶端創建的節點是0000000002,因爲它的編號不是最小的,所以獲取不到鎖,那麼它就找到它前面的一個節點0000000001 並對它設置監聽。
  • 釋放鎖,當前獲得鎖的客戶端在操作完成後刪除自己創建的節點,這樣會觸發zk的事件給其它客戶端知道,這樣其它客戶端會重新執行(步驟3)。
    舉例:加入客戶端0000000001獲取到鎖,然後客戶端0000000002加入進來獲取鎖,發現自己不是編號最小的,那麼它會監聽它前面節點的事件(0000000001的事件)然後執行步驟(3),當客戶端0000000001操作完成後刪除自己的節點,這時zk服務端會發送事件,這時客戶端0000000002會接收到該事件,然後重複步驟3直到獲取到鎖)

2.實現代碼

package com.zookeeper.base;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

public class ZookeeperClient {

    // 超時時間
    private static final int SESSION_TIMEOUT = 5000;
    // server列表
//  private static final String HOSTS = "192.168.223.144:2181,192.168.223.145:2181,192.168.223.146:2181";
    private static final String HOSTS = "127.0.0.1:2181";
    private String groupNode = "mylock";
    private String subNode = "lock-";
    private ZooKeeper zk;
    // 當前client創建的子節點
    private String thisPath;
    // 當前client等待的子節點
    private String waitPath;
    // 多線程工具類
    private CountDownLatch cdl = new CountDownLatch(1);

    public void connectZookeeper() throws Exception {
        zk = new ZooKeeper(HOSTS, SESSION_TIMEOUT, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                try{
                    if(event.getState() == KeeperState.SyncConnected) {
                        if(EventType.None == event.getType()) {
                            System.out.println("建立連接...");
                            cdl.countDown();
                        }
                        // 如果檢測到節點刪除, 並且是waitPath節點
                        else if(EventType.NodeDeleted == event.getType() &&
                                event.getPath().equals(waitPath)) {
                            // 確認thisPath是否真的是列表中最小節點
                            List<String> childrenNodes = zk.getChildren("/"+groupNode, false);
                            String thisNode = thisPath.substring(("/"+groupNode+"/").length());
                            // 排序
                            Collections.sort(childrenNodes);
                            int index = childrenNodes.indexOf(thisNode);
                            if(index == 0) {
                                // 確定是最小節點,進行業務處理
                                doSomething();
                            } else {
                                // 說明waitPath由於異常掛掉
                                // 更新waitPath
                                waitPath = "/" + groupNode + "/" +childrenNodes.get(index-1);
                                // 如果存在waitPath節點重新註冊監聽,
                                // 如果判斷waitPath已經被刪除就執行業務
                                if(zk.exists(waitPath, true) == null) {
                                    doSomething();
                                }
                            }
                        }
                    }
                }catch(Exception e) {
                    e.printStackTrace();
                }
            }
        });
        // 阻塞等待連接建立
        cdl.await();
        // 創建父節點,不能直接創建子節點
        if(zk.exists("/"+groupNode, false) == null) {
            zk.create("/"+groupNode, null, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
        // 創建子節點
        thisPath = zk.create("/"+groupNode+"/"+subNode, null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        // wait一會讓結果更清晰
        Thread.sleep(100);
        // 注意沒有必要監聽"/locks"子節點的變化情況
        List<String> childrenNodes = zk.getChildren("/"+groupNode, false);
        if(childrenNodes.size() == 1) {
            doSomething();
        } else {
            String thisNode = this.thisPath.substring(("/"+groupNode+"/").length());
            // 排序
            Collections.sort(childrenNodes);
            int index = childrenNodes.indexOf(thisNode);
            if(index == -1) {
                // never happend
            } else if (index == 0) {
                // 說明thisNode是最小的,當前client獲取鎖
                doSomething();
            } else {
                // 獲取排名比thisPath前一位節點
                this.waitPath = "/" + groupNode + "/" + childrenNodes.get(index-1);
                // 在waitPath註冊監聽事件, 當wait被刪除, zookeeper會回調監聽器process方法
                zk.getData(waitPath, true, new Stat());
            }
        }
    }

    private void doSomething() throws Exception {
        try {
            System.out.println("gain lock:"+this.thisPath);
            // 模擬做一些業務操作
            Thread.sleep(2000);

        }catch(Exception e) {
            e.printStackTrace();
        }finally { // 業務操作之後釋放節點,否則後續節點不會被提醒
            System.out.println("finshed:"+thisPath);
            // 將thisPath刪除, 監聽thisPath的client將獲得通知
            // 相當於釋放鎖
            zk.delete(this.thisPath, -1);
        }
    }

    public static void main(String[] args) throws Exception {
        for(int i=0;i<10;i++) {
            new Thread(){
                @Override
                public void run() {
                    try{
                        ZookeeperClient zc = new ZookeeperClient();
                        zc.connectZookeeper();
                    }catch(Exception e){
                        e.printStackTrace();
                    }
                }
            }.start();
        }
        Thread.sleep(Integer.MAX_VALUE);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章