什么是master选举?
为了保证服务的7*24小时可用,不能有过长的故障时间,于是我们使用服务器集群,采用master+slave。集群中有一台主机和多台备机,主机对外提供服务,备机监听主机状态,一旦主机宕机,备机必须迅速接管主机继续向外提供服务。在这个过程中从备机选出一台机器作为主机的过程就是Master选举。
master选举利用了zookeeper临时节点的临时性与唯一性与zookeeper的节点监听机制,如果节点不唯一当存在网络延迟时可能会产生脑裂现象。 临时节点随着会话的消亡而消失,同一个临时节点只能创建一个,创建失败的节点对成功的节点进行监控,一旦master会话消失,就会从失败的节点中选举出新的master。
用zkClient实现一个简单的master选举:
直接上代码:
/**
* 实体类 存储id和name 模拟争抢master的服务器
* @author bizy1
*
*/
public class UserCenter implements Serializable{
private static final long serialVersionUID = -1776114173857775665L;
private int mc_id; //机器信息
private String mc_name;//机器名称
public int getMc_id() {
return mc_id;
}
public void setMc_id(int mc_id) {
this.mc_id = mc_id;
}
public String getMc_name() {
return mc_name;
}
public void setMc_name(String mc_name) {
this.mc_name = mc_name;
}
@Override
public String toString() {
return "UserCenter{" +
"mc_id=" + mc_id +
", mc_name='" + mc_name + '\'' +
'}';
}
}
/**
* 具体实现逻辑类
* @author bizy1
*
*/
public class MasterSelector {
private ZkClient zkClient;
private final static String MASTER_PATH="/master"; //需要争抢的节点
private IZkDataListener dataListener; //注册节点内容变化
private UserCenter server; //其他服务器
private UserCenter master; //master节点
private boolean isRunning=false; //用于标记服务是否启动
private static boolean isAllStop=false; //用于标记当全部stop时,不再触发选举事件,只要有一个server开始stop,所有的删除节点的操作都不再触发监听事件
ScheduledExecutorService scheduledExecutorService= Executors.newScheduledThreadPool(1);
//构造方法 传入并设置当前线程 与 zookeeper会话 设置当前会话的监听事件
public MasterSelector(UserCenter server,ZkClient zkClient) {
System.out.println("["+server+"] 去争抢master权限");
this.server = server;
this.zkClient=zkClient;
this.dataListener= new IZkDataListener() {
@Override
public void handleDataChange(String s, Object o) throws Exception {
}
@Override
public void handleDataDeleted(String s) throws Exception {
if(!isAllStop){
//节点如果被删除, 发起选主操作
chooseMaster();
}
}
};
}
public void start(){
//开始选举
if(!isRunning){
isRunning=true;
zkClient.subscribeDataChanges(MASTER_PATH,dataListener); //注册节点监控
//调用选举方法
chooseMaster();
}
}
public void stop(){
isAllStop=true;
//停止
if(isRunning){
isRunning=false;
System.out.println(server+"将要停止了!"+isRunning);
scheduledExecutorService.shutdown();
zkClient.unsubscribeDataChanges(MASTER_PATH,dataListener); //取消节点监控
releaseMaster();
}
}
//具体选master的实现逻辑
private void chooseMaster(){
if(!isRunning){
System.out.println("当前服务没有启动");
return ;
}
try {
zkClient.createEphemeral(MASTER_PATH, server); //创建临时节点,并传入数据server,如果已经存在临时节点则表示竞争失败转到catch
master=server; //把server节点赋值给master
System.out.println(master+"->我抢到了master");
//定时器
//master释放(master 出现故障),每4秒钟释放一次
scheduledExecutorService.schedule(()->{
releaseMaster();//释放锁
},5, TimeUnit.SECONDS);
}catch (ZkNodeExistsException e){
//表示master已经存在
System.out.println(" master已经存在,"+server+"没有抢到master节点");
UserCenter userCenter=zkClient.readData(MASTER_PATH,true);
if(userCenter==null) { //master节点被删除,尚未选出新的master
System.out.println("启动操作:");
chooseMaster(); //再次获取master
}else{
master=userCenter;
}
}
}
private void releaseMaster(){
//释放锁(故障模拟过程)
//判断当前是不是master,只有master才需要释放
if(checkIsMaster()){
System.out.println("节点"+server+"将被删除");
zkClient.delete(MASTER_PATH); //删除
}
}
private boolean checkIsMaster(){
//判断当前的server是不是master
try {
UserCenter userCenter=zkClient.readData(MASTER_PATH);
if(userCenter.getMc_name().equals(server.getMc_name())){
master=userCenter;
return true;
}
return false;
} catch (Exception e) {
System.out.println("MASTER_PATH已经被删除 ,"+server+"的readData无效");
return false; //例如:当节点3删除后,节点5 stop时readData(MASTER_PATH)时,MASTER_PATH已经不存在了,会抛出异常
}
}
}
/**
* 启动类
* 启动十个客户端 进行 选举
* @author bizy1
*
*/
public class MasterChooseTest {
private final static String CONNECTSTRING="192.168.74.131:2181,192.168.74.132:2181,192.168.74.133:2181";
public static void main(String[] args) throws IOException, Exception {
List<MasterSelector> selectorLists=new ArrayList<>();
try {
//这里不需要并发操作,因为在linux启动zookeeper时也是一台台启动zookeeper
for(int i=0;i<10;i++) {
ZkClient zkClient = new ZkClient(CONNECTSTRING, 5000,5000,new SerializableSerializer());
UserCenter userCenter = new UserCenter();
userCenter.setMc_id(i);
userCenter.setMc_name("客户端:" + i);
MasterSelector selector = new MasterSelector(userCenter,zkClient);
selectorLists.add(selector);
selector.start();//触发选举操作
TimeUnit.MILLISECONDS.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
TimeUnit.SECONDS.sleep(12);
for(MasterSelector selector:selectorLists){
selector.stop();
}
}
}
}
打印结果及分析: //行为注释
//第一个启动的肯定先抢到master因为下一个启动是在0.5秒后
[UserCenter{mc_id=0, mc_name='客户端:0'}] 去争抢master权限
UserCenter{mc_id=0, mc_name='客户端:0'}->我抢到了master
//1-9启动后争抢都失败,因为master节点已经被注册
[UserCenter{mc_id=1, mc_name='客户端:1'}] 去争抢master权限
master已经存在,UserCenter{mc_id=1, mc_name='客户端:1'}没有抢到master节点
[UserCenter{mc_id=2, mc_name='客户端:2'}] 去争抢master权限
master已经存在,UserCenter{mc_id=2, mc_name='客户端:2'}没有抢到master节点
[UserCenter{mc_id=3, mc_name='客户端:3'}] 去争抢master权限
master已经存在,UserCenter{mc_id=3, mc_name='客户端:3'}没有抢到master节点
[UserCenter{mc_id=4, mc_name='客户端:4'}] 去争抢master权限
master已经存在,UserCenter{mc_id=4, mc_name='客户端:4'}没有抢到master节点
[UserCenter{mc_id=5, mc_name='客户端:5'}] 去争抢master权限
master已经存在,UserCenter{mc_id=5, mc_name='客户端:5'}没有抢到master节点
[UserCenter{mc_id=6, mc_name='客户端:6'}] 去争抢master权限
master已经存在,UserCenter{mc_id=6, mc_name='客户端:6'}没有抢到master节点
[UserCenter{mc_id=7, mc_name='客户端:7'}] 去争抢master权限
master已经存在,UserCenter{mc_id=7, mc_name='客户端:7'}没有抢到master节点
[UserCenter{mc_id=8, mc_name='客户端:8'}] 去争抢master权限
master已经存在,UserCenter{mc_id=8, mc_name='客户端:8'}没有抢到master节点
[UserCenter{mc_id=9, mc_name='客户端:9'}] 去争抢master权限
master已经存在,UserCenter{mc_id=9, mc_name='客户端:9'}没有抢到master节点
//计时器sleep5秒后释放锁,模拟master遭遇异常宕机
节点UserCenter{mc_id=0, mc_name='客户端:0'}将被删除
//新一轮抢夺 6创建了节点,6变成了新的master
master已经存在,UserCenter{mc_id=8, mc_name='客户端:8'}没有抢到master节点
master已经存在,UserCenter{mc_id=9, mc_name='客户端:9'}没有抢到master节点
UserCenter{mc_id=6, mc_name='客户端:6'}->我抢到了master
master已经存在,UserCenter{mc_id=4, mc_name='客户端:4'}没有抢到master节点
master已经存在,UserCenter{mc_id=3, mc_name='客户端:3'}没有抢到master节点
//计时器sleep5秒后释放锁,模拟master遭遇异常宕机
节点UserCenter{mc_id=6, mc_name='客户端:6'}将被删除
//5抢到了master节点
UserCenter{mc_id=5, mc_name='客户端:5'}->我抢到了master
master已经存在,UserCenter{mc_id=4, mc_name='客户端:4'}没有抢到master节点
master已经存在,UserCenter{mc_id=3, mc_name='客户端:3'}没有抢到master节点
master已经存在,UserCenter{mc_id=6, mc_name='客户端:6'}没有抢到master节点
master已经存在,UserCenter{mc_id=0, mc_name='客户端:0'}没有抢到master节点
master已经存在,UserCenter{mc_id=9, mc_name='客户端:9'}没有抢到master节点
master已经存在,UserCenter{mc_id=8, mc_name='客户端:8'}没有抢到master节点
master已经存在,UserCenter{mc_id=2, mc_name='客户端:2'}没有抢到master节点
master已经存在,UserCenter{mc_id=7, mc_name='客户端:7'}没有抢到master节点
master已经存在,UserCenter{mc_id=1, mc_name='客户端:1'}没有抢到master节点
//计时器sleep5秒后释放锁,模拟master遭遇异常宕机
//又被5抢到了
节点UserCenter{mc_id=5, mc_name='客户端:5'}将被删除
UserCenter{mc_id=5, mc_name='客户端:5'}->我抢到了master
master已经存在,UserCenter{mc_id=6, mc_name='客户端:6'}没有抢到master节点
master已经存在,UserCenter{mc_id=9, mc_name='客户端:9'}没有抢到master节点
master已经存在,UserCenter{mc_id=8, mc_name='客户端:8'}没有抢到master节点
master已经存在,UserCenter{mc_id=4, mc_name='客户端:4'}没有抢到master节点
master已经存在,UserCenter{mc_id=3, mc_name='客户端:3'}没有抢到master节点
master已经存在,UserCenter{mc_id=2, mc_name='客户端:2'}没有抢到master节点
master已经存在,UserCenter{mc_id=1, mc_name='客户端:1'}没有抢到master节点
master已经存在,UserCenter{mc_id=7, mc_name='客户端:7'}没有抢到master节点
//此时finally处sleep结束,将全部节点stop掉,其实核心是上面的master选举过程
UserCenter{mc_id=0, mc_name='客户端:0'}将要停止了!false
UserCenter{mc_id=1, mc_name='客户端:1'}将要停止了!false
UserCenter{mc_id=2, mc_name='客户端:2'}将要停止了!false
UserCenter{mc_id=3, mc_name='客户端:3'}将要停止了!false
UserCenter{mc_id=4, mc_name='客户端:4'}将要停止了!false
//由于5是master,所以多打印了一句,具体原因看代码
UserCenter{mc_id=5, mc_name='客户端:5'}将要停止了!false
节点UserCenter{mc_id=5, mc_name='客户端:5'}将被删除
//5删除后,zookeeper中没有了/master节点,所以后续的节点删除在readData时都会产生异常,这个地方//可以改进,但是懒得改进了
UserCenter{mc_id=6, mc_name='客户端:6'}将要停止了!false
MASTER_PATH已经被删除 ,readData无效
UserCenter{mc_id=7, mc_name='客户端:7'}将要停止了!false
MASTER_PATH已经被删除 ,readData无效
UserCenter{mc_id=8, mc_name='客户端:8'}将要停止了!false
MASTER_PATH已经被删除 ,readData无效
UserCenter{mc_id=9, mc_name='客户端:9'}将要停止了!false