zookeeper使用zkClient实现简单的master选举

什么是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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值