ZooKeeper 典型应用场景-Master选举
master选举
1、使用场景及结构
现在很多时候我们的服务需要7*24小时工作,假如一台机器挂了,我们希望能有其它机器顶替它继续工作。此类问题现在多采用master-salve模式,也就是常说的主从模式,正常情况下主机提供服务,备机负责监听主机状态,当主机异常时,可以自动切换到备机继续提供服务(这里有点儿类似于数据库主库跟备库,备机正常情况下只监听,不工作),这个切换过程中选出下一个主机的过程就是master选举。
对于以上提到的场景,传统的解决方式是采用一个备用节点,这个备用节点定期给当前主节点发送ping包,主节点收到ping包后会向备用节点发送应答ack,当备用节点收到应答,就认为主节点还活着,让它继续提供服务,否则就认为主节点挂掉了,自己将开始行使主节点职责。如图1所示:

图1
但这种方式会存在一个隐患,就是网络故障问题。看一下图2:

图2
也就是说,我们的主节点并没有挂掉,只是在备用节点ping主节点,请求应答的时候发生网络故障,这样我们的备用节点同样收不到应答,就会认为主节点挂掉,然后备机会启动自己的master实例。这样就会导致系统中有两个主节点,也就是双master。出现双master以后,我们的从节点会将它做的事情一部分汇报给主节点,一部分汇报给备用节点,这样服务就乱套了。为了防止这种情况出现,我们可以考虑采用zookeeper,虽然它不能阻止网络故障的出现,但它能保证同一时刻系统中只存在一个主节点。我们来看zookeeper是怎么实现的:
在此处,抢主程序是包含在服务程序中,需要程序员来手动写抢主逻辑的。
一点额外的话:zookeeper自己在集群环境下的抢主算法有三种,可以通过配置文件来设定,默认采用FastLeaderElection,不作赘述;此处主要讨论集群环境中,应用程序利用master的特点,自己选主的过程。程序自己选主,每个人都有自己的一套算法,有采用“最小编号”的,有采用类似“多数投票”的,各有优劣,本文的算法仅作演示理解使用:
结构图:

结构图解释:左侧树状结构为zookeeper集群,右侧为程序服务器。所有的服务器在启动的时候,都会订阅zookeeper中master节点的删除事件,以便在主服务器挂掉的时候进行抢主操作;所有服务器同时会在servers节点下注册一个临时节点(保存自己的基本信息),以便于应用程序读取当前可用的服务器列表。
选主原理介绍:zookeeper的节点有两种类型,持久节点跟临时节点。临时节点有个特性,就是如果注册这个节点的机器失去连接(通常是宕机),那么这个节点会被zookeeper删除。选主过程就是利用这个特性,在服务器启动的时候,去zookeeper特定的一个目录下注册一个临时节点(这个节点作为master,谁注册了这个节点谁就是master),注册的时候,如果发现该节点已经存在,则说明已经有别的服务器注册了(也就是有别的服务器已经抢主成功),那么当前服务器只能放弃抢主,作为从机存在。同时,抢主失败的当前服务器需要订阅该临时节点的删除事件,以便该节点删除时(也就是注册该节点的服务器宕机了或者网络断了之类的)进行再次抢主操作。从机具体需要去哪里注册服务器列表的临时节点,节点保存什么信息,根据具体的业务不同自行约定。选主的过程,其实就是简单的争抢在zookeeper注册临时节点的操作,谁注册了约定的临时节点,谁就是master。
ps:本文的例子中,并未用到结构图server节点下的数据。但换一种算法或者业务场景就会用到,算法比如提到的最小编号,主要逻辑是主节点挂掉后,从节点里边编号最小的成为主节点,此时会用到该节点内容。换一种业务场景:集群环境中,有很多任务要处理, 主节点负责接收任务,并根据一定算法将任务分配到不同的机器上执行;这种情况下,主节点跟从节点的职责也是不同的,主节点挂掉也会涉及到从节点进行master选举的问题。这种情况下,很显然,作为主节点需要知道当前有多少个从节点还活着,那么此时也会需要用到servers节点下的数据了。
架构图:

1)、左边区域代表zk集群
2)、右边代表3台工作服务器
它们在各自启动过程中首先会去zk集群的Servers节点下创建临时节点,并把自己的基本信息写入到临时节点,这个过程叫做服务注册。系统中的其他服务可以通过获取Servers节点的子节点列表来了解当前系统哪些服务器可用。这个过程叫做服务发现。
接着这些服务器会去尝试创建Master节点,谁能创建成功,谁就作为master向外提供服务。其他机器作为slave,所有的slave必须关注master节点的删除事件。
一个临时节点在创建它的会话失效以后会自动的被zk删除掉,而创建会话的机器宕机会直接导致会话的失效。我们可以监听master节点的失效来了解master节点是否宕机,一旦宕机,就必须发起新一轮的master选举。新选举出的master继续提供服务。
程序主体流程:

work server在启动的时候首先会注册监听master节点的删除事件 ,紧接着会尝试创建master节点,如果可以创建成功,说明自己就是master,如果不能说明当前系统中master节点已存在,其他机器争抢到了master权利,这个时候我们可以读取master节点的数据内容,如果可以读取成功,就把master的基本信息放入自己的内存变量中。如果不能,说明在读取master的瞬间master宕机了。这个时候需要发起新一轮的master 选举来争抢master权利。
应对网络抖动流程:

系统的核心类:

Work Server:对应架构图的Work Server,是主工作类。
Running Data:用于描述Work Server的基本信息。
LeaderSelectorZkClient:作为调度器来启动停止Work Server。
2、编码实现
主要有两个类,WorkServer为主服务类,RunningData用于记录运行数据。因为是简单的demo,我们只做抢master节点的编码,对于从节点应该去哪里注册服务列表信息,不作编码。
采用zkClient实现,代码如下:
WorkServer类:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.exception.ZkException;
import org.I0Itec.zkclient.exception.ZkInterruptedException;
import org.I0Itec.zkclient.exception.ZkNoNodeException;
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
import org.apache.zookeeper.CreateMode; public class WorkServer { private volatile boolean running = false; //记录服务器运行状态 private ZkClient zkClient; //开源客户端-zk客户端 private static final String MASTER_PATH = "/master"; //master节点对应在zookeeper中的节点路径 private IZkDataListener dataListener; //监听zookeeper中master节点的删除事件 private RunningData serverData; //集群中当前服务器节点的基本信息 private RunningData masterData; //集群中master节点的基本信息 private ScheduledExecutorService delayExector = Executors.newScheduledThreadPool(1);
private int delayTime = 5; public WorkServer(RunningData rd) {
this.serverData = rd;
this.dataListener = new IZkDataListener() {
public void handleDataDeleted(String dataPath) throws Exception {//节点删除事件 //takeMaster(); if (masterData != null && masterData.getName().equals(serverData.getName())) {
takeMaster();
} else {
delayExector.schedule(new Runnable() {
public void run() {
takeMaster();
}
}, delayTime, TimeUnit.SECONDS);
}
} public void handleDataChange(String dataPath, Object data)
throws Exception {//节点内容变化事件
}
};
} public ZkClient getZkClient() {
return zkClient;
} public void setZkClient(ZkClient zkClient) {
this.zkClient = zkClient;
} /**
* 服务start方法
*
* @throws Exception
*/
public void start() throws Exception {
if (running) {
throw new Exception("server has startup...");
}
running = true;
zkClient.subscribeDataChanges(MASTER_PATH, dataListener);
takeMaster();
} /**
* 服务stop方法
*
* @throws Exception
*/
public void stop() throws Exception {
if (!running) {
throw new Exception("server has stoped");
}
running = false; delayExector.shutdown(); zkClient.unsubscribeDataChanges(MASTER_PATH, dataListener); releaseMaster();
} /**
* 争抢master权利
*/
private void takeMaster() {
if (!running) {
return;
}
try {
zkClient.create(MASTER_PATH, serverData, CreateMode.EPHEMERAL);
masterData = serverData;
System.out.println(serverData.getName() + " is master");
// 以下代码作为演示,每隔5秒钟释放master权利
delayExector.schedule(new Runnable() {
public void run() {
if (checkMaster()) {
releaseMaster();
}
}
}, 5, TimeUnit.SECONDS);
} catch (ZkNodeExistsException e) {
RunningData runningData = zkClient.readData(MASTER_PATH, true);
if (runningData == null) {
takeMaster();
} else {
masterData = runningData;
}
} catch (Exception e) {
// ignore;
}
} /**
* 释放master权利
*/
private void releaseMaster() {
if (checkMaster()) {
zkClient.delete(MASTER_PATH);
}
} /**
* 检测是否是master
*/
private boolean checkMaster() {
try {
RunningData eventData = zkClient.readData(MASTER_PATH);
masterData = eventData;
if (masterData.getName().equals(serverData.getName())) {
return true;
}
return false;
} catch (ZkNoNodeException e) {
return false;
} catch (ZkInterruptedException e) {
return checkMaster();
} catch (ZkException e) {
return false;
}
}
}
RunningData类:
import java.io.Serializable;
public class RunningData implements Serializable {
private static final long serialVersionUID = 4260577459043203630L;
private Long cid;
private String name;
public Long getCid() {
return cid;
}
public void setCid(Long cid) {
this.cid = cid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
说明:在实际生产环境中,可能会由于插拔网线等导致网络短时的不稳定,也就是网络抖动。由于正式生产环境中可能server在zk上注册的信息是比较多的,而且server的数量也是比较多的,那么每一次切换主机,每台server要同步的数据量(比如要获取谁是master,当前有哪些salve等信息,具体视业务不同而定)也是比较大的。那么我们希望,这种短时间的网络抖动最好不要影响我们的系统稳定,也就是最好选出来的master还是原来的机器,那么就可以避免发现master更换后,各个salve因为要同步数据等导致的zk数据网络风暴。所以在WorkServer中,54-63行,我们抢主的时候,如果之前主机是本机,则立即抢主,否则延迟5s抢主。这样就给原来主机预留出一定时间让其在新一轮选主中占据优势,从而利于环境稳定。
测试代码:
import com.sql.zookeeper.common.ZookeeperConstant;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.serialize.SerializableSerializer; import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List; public class LeaderSelectorZkClient {
//启动的服务个数
private static final int CLIENT_QTY = 10; public static void main(String[] args) throws Exception {
//保存所有zkClient的列表
List<ZkClient> clients = new ArrayList<ZkClient>();
//保存所有服务的列表
List<WorkServer> workServers = new ArrayList<WorkServer>(); try {
for (int i = 0; i < CLIENT_QTY; ++i) {
//创建zkClient
ZkClient client = new ZkClient(ZookeeperConstant.ZK_CONNECTION_STRING, 5000, 5000, new SerializableSerializer());
clients.add(client); //创建serverData
RunningData runningData = new RunningData();
runningData.setCid(Long.valueOf(i));
runningData.setName("Client #" + i); //创建服务
WorkServer workServer = new WorkServer(runningData);
workServer.setZkClient(client); workServers.add(workServer);
workServer.start();
} System.out.println("敲回车键退出!\n");
new BufferedReader(new InputStreamReader(System.in)).readLine();
} finally {
System.out.println("Shutting down..."); for (WorkServer workServer : workServers) {
try {
workServer.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
for (ZkClient client : clients) {
try {
client.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
两次测试,本地模拟10台server,分别不启用防止网络抖动跟启动防抖动两次测试结果如下:
未启动防抖动:

启用防抖动:

可以看到,未启用的时候,断线后重新选出的主机是随机的,没规律;启用防抖动后,每次选出的master都是id为0的机器。
-----------------------------------------------------------------------------------------------------------------------------
至此,我们已经通过编码实现了简单的master选举。但是,不知你有没有发现,,,,这个选主过程的代码还真是麻烦啊!
我们只是做一个demo,其中并未考虑复杂的业务场景,但其中的 监听,异常 等代码的处理还是让我觉得有些头大,怎么办?Curator应运而生!
为了熟悉Apache Curator,接下来,将用curator来实现master选举的demo。
ZooKeeper 典型应用场景-Master选举的更多相关文章
- ZooKeeper典型应用场景
ZooKeeper典型应用场景一览 数据发布与订阅(配置中心) 发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据发布到ZK节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新.例 ...
- ZooKeeper典型应用场景一览
原文地址:http://jm-blog.aliapp.com/?p=1232 ZooKeeper典型应用场景一览 数据发布与订阅(配置中心) 发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据 ...
- ZooKeeper典型应用场景(转)
ZooKeeper是一个高可用的分布式数据管理与系统协调框架.基于对Paxos算法的实现,使该框架保证了分布式环境中数据的强一致性,也正是基于这样的特性,使得ZooKeeper解决很多分布式问题.网上 ...
- ZooKeeper典型应用场景概览
ZooKeeper是一个高可用的分布式数据管理与系统协调框架.基于对Paxos算法的实现,使该框架保证了分布式环境中数据的强一致性,也正是基于这样的特性,使得ZooKeeper解决很多分布式问题.网上 ...
- 搞懂分布式技术6:Zookeeper典型应用场景及实践
搞懂分布式技术6:Zookeeper典型应用场景及实践 一.ZooKeeper典型应用场景实践 ZooKeeper是一个高可用的分布式数据管理与系统协调框架.基于对Paxos算法的实现,使该框架保证了 ...
- ZOOKEEPER典型应用场景解析
zookeeper实现了主动通知节点变化,原子创建节点,临时节点,按序创建节点等功能.通过以上功能的组合,zookeeper能够在分布式系统中组合出很多上层功能.下面就看几个常用到的场景,及使用方式和 ...
- zookeeper典型应用场景之一:master选举
对于zookeeper这种东西,仅仅知道怎么安装是远远不够的,至少要对其几个典型的应用场景进行了解,才能比较全面的知道zk究竟能干啥,怎么玩儿,以后的日子里才能知道这货如何能为我所用.于是,有了如下的 ...
- ZooKeeper 典型应用场景
Zookeeper基础知识 1.zookeeper是一个类似hdfs的树形文件结构,zookeeper可以用来保证数据在(zk)集群之间的数据的事务性一致. 2.zookeeper有watch事件,是 ...
- ZooKeeper典型使用场景一览
场景类别 典型场景描述(ZK特性,使用方法) 应用中的具体使用 数据发布与订阅 发布与订阅即所谓的配置管理,顾名思义就是将数据发布到zk节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新 ...
随机推荐
- panda强化练习2
In [1]: import pandas as pd import numpy as np import matplotlib.pyplot as plt plt.rcParams['font.sa ...
- PIE SDK点元素的绘制
1. 功能简介 在数据的处理中会用到点元素的绘制,目前PIE SDK支持IMarkerSymbol的点元素的绘制,MarkerSymbol对象是用于修饰点状对象的符号,它包括ArrowMarkerSy ...
- PDFJs 在线预览插件
0.A.到官网 https://mozilla.github.io/pdf.js/getting_started/#download 下载最新版本B 部署到IIS 中访问 pdf.js/web/vie ...
- PowerDesigner 16.5 安装和卸载教程【含有安装文件】
1 下载 下载地址:https://pan.baidu.com/s/1kqly0d8qU-QluEagXwh53g 密码:n1a3 下载之后目录结构如下: 2 安装教程 1 安装 1.双击安装文件,如 ...
- PHP邮件发送
php带有内置的mail() 发送邮件函数,但是较为繁琐:建议上网下载一个PHPMailer:
- 命令行编译java项目
命令行编译java项目 项目名: testproj 目录 src -> cn -> busix -> test bin lib 编译项目 cd testproj javac -d . ...
- ubuntu命令行添加拥有管理员权限新用户
最近买了个服务器,只有一个root用户,天天登录挺不方便的,所以想要新建用户;之前在本地都是用界面话新建的用户,这次记录一下学习命令行新建用户的过程: 第一步 : # sudo adduser zhq ...
- webbrowser使用已过期资源的一个报错
如果webbrowser控件已更新,仍使用一个HtmlElement就会出现以下错误: 2013-12-23 17:33:48,375 [18] ERROR xx.xxx<Run>b__0 ...
- 项目视图 Project Browser
项目视图 在这个视图,你可以访问.管理你当前项目资源. 项目视图左侧面板显示项目的文件夹结构的分层列表,当从列表中单击一个文件夹,其内容会显示在面板的右边.你可以点击小三角展开或折叠文件夹,显示他包含 ...
- SpringSecurity 3.2入门(3)单用户登录
1.增加web.xml文件配置如下 <!-- 获取Spring Security session的生命周期,这个监听器会在 session 创建和销毁的时候通知 Spring Security ...