Zookeeper应用之——选举(Election)

请注意,此篇文章并不是介绍Zookeeper集群内部Leader的选举机制,而是应用程序使用Zookeeper作为选举。

使用Zookeeper进行选举,主要用到了Znode的两个性质:

  1. 临时节点(EPHEMERAL)
  2. 序列化节点(SEQUENCE)

每一个临时的序列化节点代表着一个客户端(client),也就是选民。主要的设计思路如下:

首先,创建一个选举的节点,我们叫做/election。
然后,每有一个客户端加入,就创建一个子节点/election/n_xxx,这个节点是EPHEMERAL并且SEQUENCE,xxx就是序列化产生的单调递增的数字。
在所有子节点中,序列数字做小的被选举成Leader。

上面的并不是重点,重点是Leader失败的检测,Leader失败后,一个新的客户端(client)将被选举成Leader。实现这个过程的一个最简单的方式是
所有的客户端(client)都监听Leader节点,一旦Leader节点消失,将通知所有的客户端(client)执行Leader选举过程,序列数字最小的将被选举成Leader。
这样实现看似没有问题,但是当客户端(client)数量非常庞大时,所有客户端(client)都将在/election节点执行getChildren(),这对Zookeeper
的压力是非常大的。为了避免这种“惊群效应”,我们可以让客户端只监听它前一个节点(所有序列数字比当前节点小,并且是其中最大的那个节点)。
这样,Leader节点消失后,哪个节点收到了通知,哪个节点就变成Leader,因为所有节点中,没有比它序列更小的节点了。

具体步骤如下:

  1. 使用EPHEMERAL和SEQUENCE创建节点/election/n_xxx,我们叫做z。
  2. C为/election的子节点集合,i是z的序列数字。
  3. 监听/election/n_j,j是C中小于i的最大数字。

接收到节点消失的事件后:

  1. C为新的/election的子节点集合
  2. 如果z是集合中最小的节点,则z被选举成Leader
  3. 如果z不是最小节点,则继续监听/election/n_j,j是C中小于i的最大数字。

具体代码如下:

public class Candidate implements Runnable, Watcher {
    //zk
    private ZooKeeper zk;
    //临时节点前缀
    private String perfix = "n_";
    //当前节点
    private String currentNode;
    //前一个最大节点
    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 {
            //创建临时节点
            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();
        }
    }

    /**
     * 从小到大排序临时节点
     * @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节点中的所有子节点
                List<String> children = zk.getChildren("/zookeeper/election", false);
                //所有子节点排序(从小到大)
                List<String> sortedNodes = getSortedNode(children);
                //获取最小节点
                String smallestNode = sortedNodes.get(0);
                //当前节点就是最小节点,被选举成Leader
                if (currentNode.equals("/zookeeper/election/"+smallestNode)) {
                    System.out.println(currentNode + "被选举成Leader。");
                    Thread.sleep(5000);
                    //模拟Leader节点死去
                    System.out.println(currentNode+"已离去");
                    zk.close();
                    break;
                }
                //当前节点不是最小节点,监听前一个最大节点
                else {
                    //前一个最大节点
                    lastNode = smallestNode;
                    //找到前一个最大节点,并监听
                    for (int i = 1; i < sortedNodes.size(); i++) {
                        String z = sortedNodes.get(i);
                        //找到前一个最大节点,并监听
                        if (currentNode.equals("/zookeeper/election/"+z)) {
                            zk.exists("/zookeeper/election/" + lastNode, true);
                            System.out.println(currentNode+"监听"+lastNode);
                            //等待被唤起执行Leader选举
                            synchronized (this){
                                wait();
                            }
                            break;
                        }
                        lastNode = z;
                    }
                }
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 观察器通知
     * @param event
     */
    @Override
    public void process(WatchedEvent event) {
        //监听节点删除事件
        if (event.getType().equals(Event.EventType.NodeDeleted)) {
            //被删除的节点是前一个最大节点,唤起线程执行选举
            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();
        //获取应用中的所有logger实例
        List<Logger> loggerList = loggerContext.getLoggerList();

        //遍历更改每个logger实例的级别,可以通过http请求传递参数进行动态配置
        for (ch.qos.logback.classic.Logger logger:loggerList){
            logger.setLevel(Level.toLevel("ERROR"));
        }
    }
}

运行结果如下:

/zookeeper/election/n_0000000133被选举成Leader。
/zookeeper/election/n_0000000134监听n_0000000133
/zookeeper/election/n_0000000137监听n_0000000136
/zookeeper/election/n_0000000135监听n_0000000134
/zookeeper/election/n_0000000136监听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阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容

  • 一个真正的写数据流程是怎么样的?一个真正的读数据流程是怎么样的?一个真正的同步数据流程是怎么样的?从哪里到哪里?什...
    时待吾阅读 4,001评论 0 14
  • 一、ZooKeeper的背景 1.1 认识ZooKeeper ZooKeeper---译名为“动物园管理员”。动物...
    algernoon阅读 9,043评论 1 106
  • 本文将从系统模型、序列化与协议、客户端工作原理、会话、服务端工作原理以及数据存储等方面来揭示ZooKeeper的技...
    端木轩阅读 3,775评论 0 42
  • 文/白公子 熙熙攘攘皆为利来,熙熙攘攘皆为利往。 五月的广州已经进入了酷暑模式,一个人待在家里,享受着空调带来清凉...
    Weason阅读 1,148评论 13 20
  • 张家小院 ‘叮叮叮……’ 锤子敲打声 张小北: “床前明月光,疑是地上霜。举头望明月,低头思故乡。低...
    酥油柚子阅读 526评论 1 3