Zookeeper-實戰

Master選舉

Master選舉架構

這裏寫圖片描述

  • 服務註冊——各個服務器在啓動過程中,首先會到Zookeeper節點下創建一個臨時節點,並把自己的基本信息寫入到這個臨時節點。
  • 服務發現——系統中的其他服務可以通過獲取servers節點的子節點列表,來了解當前系統哪些服務器可用。
  • 爭搶Master——嘗試創建master節點,誰能創建成功,誰就是master,其他的服務器則爲Slave。
  • 重新選舉——所有服務器必須監聽master服務器的刪除時間(因爲Zookeeper的臨時節點hui會隨着會話失效而被刪除),一旦master節點宕機,其他的節點就可立刻發現,並重新選舉。

Master爭搶流程

master選舉

ZkClient實現服務器Master爭搶

就Master爭搶做一個簡易Demo
- 服務器信息

public class ServerData implements Serializable{

    private int serverId;

    private String serverName;

    public int getServerId() {
        return serverId;
    }

    public void setServerId(int serverId) {
        this.serverId = serverId;
    }

    public String getServerName() {
        return serverName;
    }

    public void setServerName(String serverName) {
        this.serverName = serverName;
    }
}
  • Master選舉
public class MasterVote {
    //zkclient客戶端
    private ZkClient zkClient;

    //爭搶的master節點
    private static final String MASTER_NODE = "/master";

    //連接地址
    private static final String CONNECTION_STRING = "xxx.xxx.xxx.xxx:2181";

    //超時時間
    private static final int SESSION_TIMEOUT=5000;

    //爭搶master的服務器節點信息
    private ServerData serverData;

    //爭搶到master節點的服務器信息
    private ServerData masterData;

    //服務器開啓狀態
    private volatile boolean running = false;

    //master節點監聽事件
    IZkDataListener dataListener;

    private ScheduledExecutorService scheService = Executors.newScheduledThreadPool(1);

    public MasterVote(ZkClient zkclient,ServerData serverData){
        this.zkClient = zkclient;
        this.serverData = serverData;
        dataListener = new IZkDataListener() {
            @Override
            public void handleDataChange(String s, Object o) throws Exception {

            }

            @Override
            public void handleDataDeleted(String s) throws Exception {
                takeMaster();
            }
        };
    }

    /**
     * 開始爭搶master
     */
    public void start(){
        if(running){
            throw new RuntimeException("服務器在啓動狀態!");
        }
        running = true;
        zkClient.subscribeDataChanges(MASTER_NODE,dataListener);
        takeMaster();
    }

    /**
     * 停止爭搶master
     */
    public void stop(){
        if(!running){
            throw new RuntimeException("服務器在停止狀態");
        }
        running = false;
        zkClient.subscribeDataChanges(MASTER_NODE,dataListener);
        releaseMaster();
    }

    /**
     * 爭搶master節點的具體實現
     */
    private void takeMaster(){
        if(!running){
            throw new RuntimeException("服務器沒有啓動");
        }
        System.out.println(serverData.getServerName()+"開始搶master節點");
        try {
            //創建master節點成功,該服務器爲master,記錄master節點信息
            zkClient.createEphemeral(MASTER_NODE,serverData);
            masterData = serverData;
            System.out.println(masterData.getServerName()+"成功搶到master!");
            //每五秒釋放一次
            scheService.schedule(new Runnable() {
                @Override
                public void run() {
                    releaseMaster();
                }
            },5,TimeUnit.SECONDS);
        }
        //ZkNode節點已經存在,說明master節點已經存在
        catch (ZkNodeExistsException e) {
            //讀取當前master節點的信息
            ServerData currentMasterData = zkClient.readData(MASTER_NODE);
            //讀取的過程中master節點已經被釋放
            if(currentMasterData==null){
                takeMaster();
            }
//            else {
//                masterData = currentMasterData;
//            }
        }
    }

    /**
     * 釋放master節點
     */
    private void releaseMaster(){
        if(checkMaster()){
            zkClient.delete(MASTER_NODE);
            System.out.println("釋放===================================");
        }
    }

    /**
     * 校驗當前的服務器是否是master
     */
    private boolean checkMaster(){
        try {
            //讀取當前master節點數據
            ServerData currentMasterData = zkClient.readData(MASTER_NODE);
            masterData = currentMasterData;
            //master的服務名等於當前爭搶的服務器名
            if (masterData.getServerName().equals(masterData.getServerName())) {
                return true;
            }
            else {
                return false;
            }
        }
        catch (ZkNoNodeException e) {
            return false;
        }
        //因網絡打斷出現的異常
        catch (ZkInterruptedException e) {
            return checkMaster();
        }
        catch (Exception e) {
            return false;
        }
    }
  • 測試
    使用信號量模擬多個服務同時開始爭搶
public static void main(String[] args){
        ExecutorService service = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(10);
        for(int i = 0 ; i < 10 ;i++){
            final int idx= i;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try{
                        semaphore.acquire();
                        //初始化
                        ZkClient zk = new ZkClient(CONNECTION_STRING,SESSION_TIMEOUT,
                                SESSION_TIMEOUT,new SerializableSerializer());
                        //定義爭搶的服務器信息
                        ServerData serverData = new ServerData();
                        serverData.setServerId(idx);
                        serverData.setServerName("#server-"+idx);

                        MasterVote mv = new MasterVote(zk,serverData);
                        //開始爭搶
                        mv.start();
                        semaphore.release();
                    }catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            service.execute(runnable);
        }
        service.shutdown();
    }
  • 結果
#server-3開始搶master節點
#server-1開始搶master節點
#server-6開始搶master節點
#server-8開始搶master節點
#server-4開始搶master節點
#server-9開始搶master節點
#server-2開始搶master節點
#server-0開始搶master節點
#server-7開始搶master節點
#server-5開始搶master節點
#server-4成功搶到master!
釋放===================================
#server-0開始搶master節點
#server-3開始搶master節點
#server-8開始搶master節點
#server-9開始搶master節點
#server-2開始搶master節點
#server-5開始搶master節點
#server-6開始搶master節點
#server-7開始搶master節點
#server-1開始搶master節點
#server-4開始搶master節點
#server-8成功搶到master!

分佈式鎖

分佈式鎖包含:共享鎖,排他鎖。
- 排他鎖(Exclusive Locks)——又稱爲寫鎖或獨佔鎖,需要等待其它所有的鎖全部釋放才能爭搶,且僅當持有該鎖時才能進行寫操作。
- 共享鎖(Shared Locks) ——又稱爲讀鎖,所有操作都可持有讀鎖,但這些鎖是有序的,根據順序依次進行讀操作。

原生API實現共享鎖

獲取子節點列表,如果自己創建的節點值最小,則持有鎖,並進行操作,待操作完成釋放鎖;否則等待並監聽比自己小的節點。
共享鎖
下面使用原生API模擬一次共享鎖的競爭過程。
- DistributeSharedLock

public class DistributeSharedLock implements Watcher{
    //原生API
    ZooKeeper zooKeeper = null;
    //定義根節點
    private String root = "/locks";
    //當前獲取節點
    private String currentZnode;
    //當前等待節點
    private String waitZnode;
    //發令槍
    private CountDownLatch countDownLatch;
    //連接地址
    private static final String CONNECTION_STRING = "xxx.xxx.xxx.xxx:2181,xxx.xxx.xxx.xxx:2182,xxx.xxx.xxx.xxx:2183";
    //超時時間
    private static final int SESSION_TIMEOUT=5000;

    /**
     * 構造函數
     * @param config
     */
    public DistributeSharedLock(String config){
        try {
            zooKeeper = new ZooKeeper(config,SESSION_TIMEOUT,this);
            //查看root節點是否存在
            Stat stat = zooKeeper.exists(root,false);
            //不存在則創建根節點
            if(stat == null){
                zooKeeper.create(root,new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    /**
     * 獲取鎖
     */
    public void lock(){
        if (tryLock()) {
            System.out.println("Thread " + Thread.currentThread().getName()+" hold lock!");
            return;
        }

        try {
            //等待並獲取鎖
            waitLock(waitZnode,SESSION_TIMEOUT);
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 釋放鎖
     */
    public void unLock(){
        System.out.println("Thread " + Thread.currentThread().getName()+" un lock!");
        try {
            zooKeeper.delete(currentZnode,-1);
            currentZnode=null;
            zooKeeper.close();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    private boolean tryLock(){
        //   /locks/lock_0000000001
        String spliteStr = "lock_";
        try {
            currentZnode = zooKeeper.create(root+"/"+spliteStr,new byte[0],ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
            System.out.println("Thread " + Thread.currentThread().getName()+" create success: " + currentZnode);
            //獲取根節點下所有子節點
            List<String> childNodes = zooKeeper.getChildren(root,false);
            //排序
            Collections.sort(childNodes);
            //如果當前節點的值是最小值,則獲得鎖
            if(currentZnode.equals(root+"/"+childNodes.get(0))){
                return true;
            }

            //否則,監聽比自己小的節點
            String subNode = currentZnode.substring(currentZnode.lastIndexOf("/")+1);//lock_0000000003
            waitZnode = childNodes.get(Collections.binarySearch(childNodes,subNode)-1);//lock_0000000002
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    private boolean waitLock(String lower,long waitTime) throws KeeperException, InterruptedException {
        Stat stat = zooKeeper.exists(root + "/" + lower, true);
        //存在比自己小的節點,開始等待
        if (stat != null) {
            System.out.println("Thread " + Thread.currentThread().getName() + " waiting for: " + root + "/" + lower);
            this.countDownLatch = new CountDownLatch(1); //實例化計數器,讓當前的線程等待
            this.countDownLatch.await(waitTime, TimeUnit.MILLISECONDS);
            this.countDownLatch = null;
        }

        return true;
    }

    /**
     * 比當前節點小的臨時節點刪除時觸發,計數器-1
     * @param watchedEvent
     */
    @Override
    public void process(WatchedEvent watchedEvent) {
        if(this.countDownLatch!=null){ //如果計數器不爲空話話,釋放計數器鎖
            this.countDownLatch.countDown();
        }
    }
}
  • 測試
public static void main(String[] args) {
        ExecutorService executorService= Executors.newCachedThreadPool();
        final Semaphore semaphore=new Semaphore(10);
        for(int i=0;i<10;i++){
            Runnable runnable=new Runnable() {
                @Override
                public void run() {
                    try {
                        semaphore.acquire();
                        DistributeSharedLock sharedLock = new DistributeSharedLock(CONNECTION_STRING);
                        sharedLock.lock();
                        //業務代碼
                        Thread.sleep(3000);
                        sharedLock.unLock();
                        semaphore.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            executorService.execute(runnable);
        }
        executorService.shutdown();
    }
  • 結果
Thread pool-1-thread-4 create success: /locks/lock_0000000123
Thread pool-1-thread-10 create success: /locks/lock_0000000122
Thread pool-1-thread-7 create success: /locks/lock_0000000121
Thread pool-1-thread-6 create success: /locks/lock_0000000128
Thread pool-1-thread-2 create success: /locks/lock_0000000129
Thread pool-1-thread-5 create success: /locks/lock_0000000130
Thread pool-1-thread-8 create success: /locks/lock_0000000125
Thread pool-1-thread-9 create success: /locks/lock_0000000127
Thread pool-1-thread-3 create success: /locks/lock_0000000126
Thread pool-1-thread-1 create success: /locks/lock_0000000124
Thread pool-1-thread-7 hold lock!
Thread pool-1-thread-4 waiting for: /locks/lock_0000000122
Thread pool-1-thread-6 waiting for: /locks/lock_0000000127
Thread pool-1-thread-5 waiting for: /locks/lock_0000000129
Thread pool-1-thread-8 waiting for: /locks/lock_0000000124
Thread pool-1-thread-2 waiting for: /locks/lock_0000000128
Thread pool-1-thread-9 waiting for: /locks/lock_0000000126
Thread pool-1-thread-10 waiting for: /locks/lock_0000000121
Thread pool-1-thread-1 waiting for: /locks/lock_0000000123
Thread pool-1-thread-3 waiting for: /locks/lock_0000000125
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章