Zookeeper应用之——选举(Election)
请注意,此篇文章并不是介绍Zookeeper集群内部Leader的选举机制,而是应用程序使用Zookeeper作为选举。
使用Zookeeper进行选举,主要用到了Znode的两个性质:
- 临时节点(EPHEMERAL)
- 序列化节点(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,因为所有节点中,没有比它序列更小的节点了。
具体步骤如下:
- 使用EPHEMERAL和SEQUENCE创建节点/election/n_xxx,我们叫做z。
- C为/election的子节点集合,i是z的序列数字。
- 监听/election/n_j,j是C中小于i的最大数字。
接收到节点消失的事件后:
- C为新的/election的子节点集合
- 如果z是集合中最小的节点,则z被选举成Leader
- 如果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。
Zookeeper应用之——选举(Election)的更多相关文章
- 【分布式】Zookeeper的Leader选举
一.前言 前面学习了Zookeeper服务端的相关细节,其中对于集群启动而言,很重要的一部分就是Leader选举,接着就开始深入学习Leader选举. 二.Leader选举 2.1 Leader选举概 ...
- 简单理解Zookeeper的Leader选举【转】
Leader选举是保证分布式数据一致性的关键所在.Leader选举分为Zookeeper集群初始化启动时选举和Zookeeper集群运行期间Leader重新选举两种情况.在讲解Leader选举前先了解 ...
- zookeeper应用 - leader选举 锁
模拟leader选举: 1.zookeeper服务器上有一个/leader节点 2.在/leader节点下创建短暂顺序节点/leader/lock-xxxxxxx 3.获取/leader的所有子节点并 ...
- 模拟使用zookeeper实现master选举
1.模拟选举机器类 package com.karat.cn.zookeeperAchieveLock.zkclient; import java.io.Serializable; /** * 选举的 ...
- Zookeeper系列(十一)zookeeper的Leader选举详解(核心之一)
作者:leesf 掌控之中,才会成功:掌控之外,注定失败. 出处:http://www.cnblogs.com/leesf456/p/6107600.html尊重原创,奇文共欣赏: 一.前言 前 ...
- 温故知新-快速理解zookeeper功能&应用&选举机制
文章目录 zookeeper简介 什么是zookeeper zookeeper应用场景 zookeeper特点 zookeeper的角色 zookeeper的数据模型 节点数据结构 节点类型 zook ...
- 【分布式】Zookeeper的Leader选举-选举过程介绍(经典的Paxos算法解析)
一.前言 前面学习了Zookeeper服务端的相关细节,其中对于集群启动而言,很重要的一部分就是Leader选举,接着就开始深入学习Leader选举. 二.Leader选举 2.1 Leader选举概 ...
- 简单理解Zookeeper的Leader选举
Leader选举是保证分布式数据一致性的关键所在.Leader选举分为Zookeeper集群初始化启动时选举和Zookeeper集群运行期间Leader重新选举两种情况.在讲解Leader选举前先了解 ...
- 面试官:说一说Zookeeper中Leader选举机制
哈喽!大家好,我是小奇,一位不靠谱的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 今天又是一个阳光明媚的一天,我又 ...
随机推荐
- DirectX using C++_error X3539:ps1_x is no longer supported...解决方案
问题来源 在研究HLSL时编译一个demo出现了error X3539的问题 解决方案 将代码中的ps_1_1 改为ps_2_0 PixelShader = compile ps_1_1 PS(); ...
- 更改Nginx网站根目录以及导致的403 forbidden问题解决
版权声明:本文为博主原创文章,未经博主允许不得转载. 一.更改根目录 Nginx默认网站根目录为/usr/local/nginx/html,要将它改成/home/fuxiao/www 更改方法: ...
- Hive学习笔记——安装和内部表CRUD
1.首先需要安装Hadoop和Hive 安装的时候参考 http://blog.csdn.net/jdplus/article/details/46493553 安装的版本是apache-hive-2 ...
- 【GIS】无人机相关技术(转)
---------------------------------------------------------------------------------------------------G ...
- c++ 模板相关
https://blog.csdn.net/lezardfu/article/details/60466161 https://www.cnblogs.com/ymy124/p/3632634.htm ...
- Ionic异常及解决
1. 编译时提示: ERROR: In <declare-styleable> FontFamilyFont, unable to find attribute android:fontV ...
- 添加ll命令
$ vim ~/.bashrcalias ll='ls -l' #加入此行 ps:加入后肯能无法当场起作用,执行该句: source ~/.bashrc
- 11 vs2015 连接oracle 11g 数据库及相关问题
1.下载Oracle Developer Tools for Visual Studio 2015 ,网址如下. http://www.oracle.com/technetwork/topics/do ...
- 多线程之批量插入小demo
多线程之批量插入 背景 昨天在测试mysql的两种批量更新时,由于需要入库大量测试数据,反复执行插入脚本,过程繁琐,档次很低,测试完后我就想着写个批量插入的小demo,然后又想写个多线程的批量插入的d ...
- 转CB大佬的几个有用的MySQL知识
1.find_in_set函数 find_in_set(str,strlist); str是一个字符串 strlist是字符串列表--一个有多个子链被“,”分开的字符串 有多种情况: a.str为nu ...