zookeeper選舉master瞭解

zookeeper廣泛用於分佈式服務中,比如選舉。這裏簡單介紹下,算是入門。

基本概念

我們知道zookeeper的結構是樹形結構

1.集羣host啓動後的監聽/master節點的刪除事件
2.各服務器host嘗試創建master,成功則把自己的信息存在master上,失敗則讀取master節點信息
3.服務註冊:各服務器host在/serverList節點下創建子節點,並把自己的信息存在節點上
4.服務發現:服務消費者方查詢/serverList子節點列表,獲取服務提供方各個host信息

這個就是利用zookeeper選舉的基本思路。

不過上邊的方案還是有問題的:即驚羣效應。當服務的機器數量比較多的時候,觸發了選舉,此時大量的請求進入zookeeper會產生比較大的壓力。

改進思路還是用zookeeper的臨時順序節點:多個機器同時向該根路徑下創建臨時順序節點,沒搶到Leader的節點都監聽前一個節點的刪除事件,這樣在前一個節點刪除後進行重新搶主。這樣一般只會有一個機器去選舉成爲leader,如果多個機器同時掛掉,參與選主的機器也不會太多。

recipes實現

基於這個思路,recipes提供了兩種實現選舉的方案:LeaderLatch與LeaderSelector。成爲leader後都有對應的Listener回調。

LeaderLatch

LeaderLatch配合LeaderLatchListener使用

示例:

public class LeadLatchMain {
    public static void main(String[] args) throws Exception {

        List<LeaderLatch> leaders = new ArrayList<LeaderLatch>();
        for (int i = 0; i < 10; i++) {
            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
            CuratorFramework client = CuratorFrameworkFactory.builder()
                    .connectString("172.16.59.154:2181").sessionTimeoutMs(5000).connectionTimeoutMs(5000).retryPolicy(retryPolicy)
                    .namespace("base")
                    .build();

            String name = "client" + i;

            // 指定客戶端和選舉路徑
            LeaderLatch leader = new LeaderLatch(client, "/master");
            leader.addListener(new LeaderLatchListener() {

                @Override
                public void isLeader() {
                    // 如果成爲leader了,則回調該方法
                    System.out.println("isLeader " + name + ", Id :" + leader.getOurPath());

                    try {
                        List<String> children = client.getChildren().forPath("/master");
                        children.forEach(c -> System.out.println(c));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void notLeader() {
                    System.out.println("notLeader " + name);
                }
            });

            leaders.add(leader);

            client.start();// 連接zookeeper服務器

            leader.start();// 開始參與選舉
        }

//        checkLeader(leaders);

        Thread.sleep(Integer.MAX_VALUE);
    }

    private static void checkLeader(List<LeaderLatch> leaderLatchList) throws Exception {
        //Leader選舉需要時間 等待10秒
        Thread.sleep(10000);
        for (int i = 0; i < leaderLatchList.size(); i++) {
            LeaderLatch leaderLatch = leaderLatchList.get(i);
            //通過hasLeadership()方法判斷當前節點是否是leader
            if (leaderLatch.hasLeadership()) {
                System.out.println("當前leader:" + leaderLatch.getId());
                // 釋放leader權限 重新進行搶主
                leaderLatch.close();
                checkLeader(leaderLatchList);
            }
        }
    }
}

接下來一起看下源碼實現:點開LeaderLatch 的start 方法,進去。

最主要的實現是這個函數

    private void checkLeadership(List<String> children) throws Exception {
        if ( debugCheckLeaderShipLatch != null ) {
            debugCheckLeaderShipLatch.await();
        }

        final String localOurPath = ourPath.get();
        // 節點按編號排序
        List<String> sortedChildren = LockInternals.getSortedChildren(LOCK_NAME, sorter, children);
        //  獲取當前子節點對應的序號
        int ourIndex = (localOurPath != null) ? sortedChildren.indexOf(ZKPaths.getNodeFromPath(localOurPath)) : -1;
        if ( ourIndex < 0 ) {
            log.error("Can't find our node. Resetting. Index: " + ourIndex);
            reset();// 當前節點不在子節點列表內,選舉失敗
        }
        else if ( ourIndex == 0 ) {
            // 當前節點編號最小, 設當前節點爲leader
            setLeadership(true);
        }
        else {
            // 沒有成爲leader,因此獲取前一個節點的路徑,並註冊監聽
            String watchPath = sortedChildren.get(ourIndex - 1);
            Watcher watcher = new Watcher() {
                @Override
                public void process(WatchedEvent event)
                {
                    // 當且僅當當前節點狀態爲STARTED,且被監聽的前一個節點發生刪除,重新進入getChildren 進行選舉
                    if ( (state.get() == State.STARTED) && (event.getType() == Event.EventType.NodeDeleted) && (localOurPath != null) ) {
                        try {
                            getChildren();
                        } catch ( Exception ex ) {
                            ThreadUtils.checkInterrupted(ex);
                            log.error("An error occurred checking the leadership.", ex);
                        }
                    }
                }
            };

            BackgroundCallback callback = new BackgroundCallback() {
                @Override
                public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
                    // 註冊失敗,則重新進入選舉
                    if ( event.getResultCode() == KeeperException.Code.NONODE.intValue() ) {
                        // previous node is gone - reset
                        reset();
                    }
                }
            };
            // use getData() instead of exists() to avoid leaving unneeded watchers which is a type of resource leak
            client.getData().usingWatcher(watcher).inBackground(callback).forPath(ZKPaths.makePath(latchPath, watchPath));
        }
    }

 

 

 

LeaderSelector

LeaderSelector與 LeaderSelectorListener,LeaderSelectorListenerAdapter一期使用

public class LeadSelectorMain {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
            CuratorFramework client = CuratorFrameworkFactory.builder()
                    .connectString("172.16.59.154:2181").sessionTimeoutMs(5000).connectionTimeoutMs(5000).retryPolicy(retryPolicy)
                    .namespace("base")
                    .build();

            client.start();// 連接
            String name = "client" + i;

            // 0.LeaderSelector 的構造參數 LeaderSelectorListener 都會被包裝成 WrappedListener
            // 1.此處使用LeaderSelectorListenerAdapter,它實現了stateChanged 函數,當客戶端與zk失連後,拋出 CancelLeadershipException 異常
            // 2.WrappedListener 捕獲該異常後,會自動取消領導權
            LeaderSelector leaderSelector = new LeaderSelector(client, "/master", new LeaderSelectorListenerAdapter() {

                @Override
                public void takeLeadership(CuratorFramework client) throws Exception {
                    // 官方文檔:http://curator.apache.org/getting-started.html
                    // this callback will get called when you are the leader
                    //  do whatever leader work you need to and only exit
                    //  this method when you want to relinquish leadership
                    System.out.println(name + " 成爲leader了");// 也就是說,該客戶端成爲Leader後,該方法會被回調
                    // sleep 10秒
                    Thread.sleep(10000);
                    System.out.println(name + " 放棄成爲leader");// 退出 takeLeadership 方法後,就放棄成爲leader
                }
            });


            //放棄領導權之後,自動再次競選
            leaderSelector.autoRequeue();// not required, but this is behavior that you will probably expect
            leaderSelector.start();
        }
        System.out.println("log end----------");
        Thread.sleep(Integer.MAX_VALUE);
    }
}

開發人員只需要在takeLeadership 函數中實現自己的代碼記錄。

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章