Zookeeper應用之——選舉(Election)

請注意戚炫,此篇文章并不是介紹Zookeeper集群內部Leader的選舉機制猜旬,而是應用程序使用Zookeeper作為選舉桩砰。

使用Zookeeper進行選舉阶捆,主要用到了Znode的兩個性質:

  1. 臨時節(jié)點(EPHEMERAL)
  2. 序列化節(jié)點(SEQUENCE)

每一個臨時的序列化節(jié)點代表著一個客戶端(client)凌节,也就是選民钦听。主要的設計思路如下:

首先,創(chuàng)建一個選舉的節(jié)點倍奢,我們叫做/election朴上。
然后,每有一個客戶端加入卒煞,就創(chuàng)建一個子節(jié)點/election/n_xxx痪宰,這個節(jié)點是EPHEMERAL并且SEQUENCE,xxx就是序列化產生的單調遞增的數字畔裕。
在所有子節(jié)點中衣撬,序列數字做小的被選舉成Leader。

上面的并不是重點扮饶,重點是Leader失敗的檢測具练,Leader失敗后,一個新的客戶端(client)將被選舉成Leader甜无。實現這個過程的一個最簡單的方式是
所有的客戶端(client)都監(jiān)聽Leader節(jié)點扛点,一旦Leader節(jié)點消失,將通知所有的客戶端(client)執(zhí)行Leader選舉過程岂丘,序列數字最小的將被選舉成Leader陵究。
這樣實現看似沒有問題,但是當客戶端(client)數量非常龐大時元潘,所有客戶端(client)都將在/election節(jié)點執(zhí)行getChildren()畔乙,這對Zookeeper
的壓力是非常大的。為了避免這種“驚群效應”翩概,我們可以讓客戶端只監(jiān)聽它前一個節(jié)點(所有序列數字比當前節(jié)點小牲距,并且是其中最大的那個節(jié)點)。
這樣钥庇,Leader節(jié)點消失后牍鞠,哪個節(jié)點收到了通知,哪個節(jié)點就變成Leader评姨,因為所有節(jié)點中难述,沒有比它序列更小的節(jié)點了。

具體步驟如下:

  1. 使用EPHEMERAL和SEQUENCE創(chuàng)建節(jié)點/election/n_xxx吐句,我們叫做z胁后。
  2. C為/election的子節(jié)點集合,i是z的序列數字嗦枢。
  3. 監(jiān)聽/election/n_j攀芯,j是C中小于i的最大數字。

接收到節(jié)點消失的事件后:

  1. C為新的/election的子節(jié)點集合
  2. 如果z是集合中最小的節(jié)點文虏,則z被選舉成Leader
  3. 如果z不是最小節(jié)點侣诺,則繼續(xù)監(jiān)聽/election/n_j殖演,j是C中小于i的最大數字。

具體代碼如下:

public class Candidate implements Runnable, Watcher {
    //zk
    private ZooKeeper zk;
    //臨時節(jié)點前綴
    private String perfix = "n_";
    //當前節(jié)點
    private String currentNode;
    //前一個最大節(jié)點
    private String lastNode;

    /**
     * 構造函數
     * @param address zk地址
     */
    public Candidate(String address) {
        try {
            this.zk = new ZooKeeper(address, 3000, this);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 加入選舉
     */
    @Override
    public void run() {
        try {
            //創(chuàng)建臨時節(jié)點
            currentNode = zk.create("/zookeeper/election/" + perfix, Thread.currentThread().getName().getBytes(),
                    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            //選舉
            election();
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 從小到大排序臨時節(jié)點
     * @param children
     * @return
     */
    private List<String> getSortedNode(List<String> children) {
        return children.stream().sorted(((o1, o2) -> {
            String sequence1 = o1.split(perfix)[1];
            String sequence2 = o2.split(perfix)[1];
            BigDecimal decimal1 = new BigDecimal(sequence1);
            BigDecimal decimal2 = new BigDecimal(sequence2);
            int result = decimal1.compareTo(decimal2);
            return result;
        })).collect(toList());
    }

    /**
     * 選舉過程
     */
    private void election(){
        try{
            while (true){
                //獲取/election節(jié)點中的所有子節(jié)點
                List<String> children = zk.getChildren("/zookeeper/election", false);
                //所有子節(jié)點排序(從小到大)
                List<String> sortedNodes = getSortedNode(children);
                //獲取最小節(jié)點
                String smallestNode = sortedNodes.get(0);
                //當前節(jié)點就是最小節(jié)點年鸳,被選舉成Leader
                if (currentNode.equals("/zookeeper/election/"+smallestNode)) {
                    System.out.println(currentNode + "被選舉成Leader趴久。");
                    Thread.sleep(5000);
                    //模擬Leader節(jié)點死去
                    System.out.println(currentNode+"已離去");
                    zk.close();
                    break;
                }
                //當前節(jié)點不是最小節(jié)點,監(jiān)聽前一個最大節(jié)點
                else {
                    //前一個最大節(jié)點
                    lastNode = smallestNode;
                    //找到前一個最大節(jié)點搔确,并監(jiān)聽
                    for (int i = 1; i < sortedNodes.size(); i++) {
                        String z = sortedNodes.get(i);
                        //找到前一個最大節(jié)點彼棍,并監(jiān)聽
                        if (currentNode.equals("/zookeeper/election/"+z)) {
                            zk.exists("/zookeeper/election/" + lastNode, true);
                            System.out.println(currentNode+"監(jiān)聽"+lastNode);
                            //等待被喚起執(zhí)行Leader選舉
                            synchronized (this){
                                wait();
                            }
                            break;
                        }
                        lastNode = z;
                    }
                }
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 觀察器通知
     * @param event
     */
    @Override
    public void process(WatchedEvent event) {
        //監(jiān)聽節(jié)點刪除事件
        if (event.getType().equals(Event.EventType.NodeDeleted)) {
            //被刪除的節(jié)點是前一個最大節(jié)點,喚起線程執(zhí)行選舉
            if (event.getPath().equals("/zookeeper/election/" + lastNode)) {
                System.out.println(currentNode+"被喚起");
                synchronized (this){
                    notify();
                }
            }
        }
    }
}

我們將啟動5個線程作為參選者妥箕,模擬每一個Leader死去滥酥,并重新選舉的過程。啟動程序如下:

public class Application {

    private static final String ADDRESS = "149.28.37.147:2181";

    public static void main(String[] args) throws InterruptedException {
        setLog();
        ExecutorService es = Executors.newFixedThreadPool(5);
        for (int i=0;i<5;i++){
            es.execute(new Candidate(ADDRESS));
        }
        es.shutdown();
    }

    /**
     * 設置log級別為Error
     */
    public static void setLog(){
        //1.logback
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        //獲取應用中的所有l(wèi)ogger實例
        List<Logger> loggerList = loggerContext.getLoggerList();

        //遍歷更改每個logger實例的級別,可以通過http請求傳遞參數進行動態(tài)配置
        for (ch.qos.logback.classic.Logger logger:loggerList){
            logger.setLevel(Level.toLevel("ERROR"));
        }
    }
}

運行結果如下:

/zookeeper/election/n_0000000133被選舉成Leader畦幢。
/zookeeper/election/n_0000000134監(jiān)聽n_0000000133
/zookeeper/election/n_0000000137監(jiān)聽n_0000000136
/zookeeper/election/n_0000000135監(jiān)聽n_0000000134
/zookeeper/election/n_0000000136監(jiān)聽n_0000000135
/zookeeper/election/n_0000000133已離去
/zookeeper/election/n_0000000134被喚起
/zookeeper/election/n_0000000134被選舉成Leader坎吻。
/zookeeper/election/n_0000000134已離去
/zookeeper/election/n_0000000135被喚起
/zookeeper/election/n_0000000135被選舉成Leader。
/zookeeper/election/n_0000000135已離去
/zookeeper/election/n_0000000136被喚起
/zookeeper/election/n_0000000136被選舉成Leader宇葱。
/zookeeper/election/n_0000000136已離去
/zookeeper/election/n_0000000137被喚起
/zookeeper/election/n_0000000137被選舉成Leader瘦真。
/zookeeper/election/n_0000000137已離去

Zookeeper作為選舉的應用就介紹完了,項目示例請參考:https://github.com/liubo-tech/zookeeper-application黍瞧。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末诸尽,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子印颤,更是在濱河造成了極大的恐慌您机,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件年局,死亡現場離奇詭異际看,居然都是意外死亡,警方通過查閱死者的電腦和手機矢否,發(fā)現死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門仲闽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人僵朗,你說我怎么就攤上這事赖欣。” “怎么了验庙?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵顶吮,是天一觀的道長。 經常有香客問我粪薛,道長云矫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任汗菜,我火速辦了婚禮让禀,結果婚禮上,老公的妹妹穿的比我還像新娘陨界。我一直安慰自己巡揍,他們只是感情好,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布菌瘪。 她就那樣靜靜地躺著腮敌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪俏扩。 梳的紋絲不亂的頭發(fā)上糜工,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天,我揣著相機與錄音录淡,去河邊找鬼捌木。 笑死,一個胖子當著我的面吹牛嫉戚,可吹牛的內容都是我干的刨裆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼彬檀,長吁一口氣:“原來是場噩夢啊……” “哼帆啃!你這毒婦竟也來了?” 一聲冷哼從身側響起窍帝,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤努潘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后坤学,有當地人在樹林里發(fā)現了一具尸體疯坤,經...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年拥峦,在試婚紗的時候發(fā)現自己被綠了贴膘。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡略号,死狀恐怖刑峡,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情玄柠,我是刑警寧澤突梦,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站羽利,受9級特大地震影響宫患,放射性物質發(fā)生泄漏。R本人自食惡果不足惜这弧,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一娃闲、第九天 我趴在偏房一處隱蔽的房頂上張望虚汛。 院中可真熱鬧,春花似錦皇帮、人聲如沸卷哩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽将谊。三九已至,卻和暖如春渐白,著一層夾襖步出監(jiān)牢的瞬間尊浓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工纯衍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留栋齿,地道東北人。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓托酸,卻偏偏與公主長得像褒颈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子励堡,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內容

  • 一個真正的寫數據流程是怎么樣的谷丸?一個真正的讀數據流程是怎么樣的?一個真正的同步數據流程是怎么樣的应结?從哪里到哪里刨疼?什...
    時待吾閱讀 4,017評論 0 14
  • 一、ZooKeeper的背景 1.1 認識ZooKeeper ZooKeeper---譯名為“動物園管理員”鹅龄。動物...
    algernoon閱讀 9,071評論 1 106
  • 本文將從系統(tǒng)模型扮休、序列化與協議迎卤、客戶端工作原理、會話玷坠、服務端工作原理以及數據存儲等方面來揭示ZooKeeper的技...
    端木軒閱讀 3,804評論 0 42
  • 文/白公子 熙熙攘攘皆為利來蜗搔,熙熙攘攘皆為利往。 五月的廣州已經進入了酷暑模式八堡,一個人待在家里樟凄,享受著空調帶來清涼...
    Weason閱讀 1,157評論 13 20
  • 張家小院 ‘叮叮叮……’ 錘子敲打聲 張小北: “床前明月光兄渺,疑是地上霜缝龄。舉頭望明月,低頭思故鄉(xiāng)。低...
    酥油柚子閱讀 538評論 1 3