zookeeper的选举模式在工作中的尝试使用

本文介绍如何利用Zookeeper实现分布式环境下定时任务的单例运行。通过在两台服务器间进行master选举,确保任何时候仅有一台服务器执行定时任务,另一台作为备机。文章详细解释了选举算法的具体步骤,并提供了相关代码示例。
摘要由CSDN通过智能技术生成

学习了一些zookeeper常用使用场景,在工作中正好将其应用。这里介绍一下项目背景:
该项目在有两台服务器跑定时任务,但希望每次只有其中一台服务器在跑任务,另一台服务器作为备机,且两台任何一台服务器down掉后,另一台会接替运行这个定时任务。

我当时正好学习了运用zookeeper进行选举的案例,就打算在本项目中运用一下,通过选举只有master服务器才有资格跑任务。

具体算法步骤:
1、封装每台服务器的信息NodeData,包含服务器名字和ip。
2、通过ServletContextListener实现在web服务器启动的时候,向zookeeper服务器注册自己,订阅/master节点的改变事件,并尝试争抢master权利。
3、每个定时任务代码中判断当前服务器是不是master,如果是,则处理定时任务的业务逻辑,否则结束这个任务。

   a. 争抢master权利就是在zookeepr的/master节点创建临时节点,如果创建成功,
     则表示自己争抢到了mater权利,该服务器为master服务器,该服务器跑定时任务。
   b. 如果上述步骤创建节点失败(节点已存在),则表示没有抢到master权利,说明其他服务器在运行定时任务。
   c. 当某台服务器down掉(比如重启),这时由于每台服务器启动的时候,都注册了/master节点的改变事件,
      所以这个时候,另外一台服务器就会监听到,然后尝试争抢master权利

流程图:

这里写图片描述

代码:

master的选举列出来如下:



import java.io.Serializable;
/**
 * 服务器的信息
 * @author kxl
 *
 */
public class NodeData implements Serializable{

    private static final long serialVersionUID = 1L;
    private String ip;  
    private String name;
    public NodeData(String ip,String name){

        this.ip=ip;
        this.name=name;
    }
    public String getIp() {
        return ip;
    }
    public void setIp(String ip) {
        this.ip = ip;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "NodeData [ip=" + ip + ", name=" + name + "]";
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((ip == null) ? 0 : ip.hashCode());
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        NodeData other = (NodeData) obj;
        if (ip == null) {
            if (other.ip != null)
                return false;
        } else if (!ip.equals(other.ip))
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }



}

zookeeper的工具类:



import java.net.InetAddress;
import java.net.UnknownHostException;

import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.serialize.SerializableSerializer;
import org.aspectj.apache.bcel.classfile.Constant;


/**
 * zookeeper的工具类
 * @author kexiaolin
 *
 */
public class ZookeeperUtil {
    private static final String MASTER_PATH = "/master";
    private static ZkClient zkClient;
    static {
        String address ="10.211.27.136";
        zkClient = new ZkClient(address, 5000, 5000, new SerializableSerializer());
    }
    /**
     * 获取服务器的信息,封装NodeData类
     * @return
     */
    public static NodeData getServerInfo() {

        String ip = null;
        String name = null;
        NodeData rd = null;
        try {
            ip = InetAddress.getLocalHost().getHostAddress();
            name = InetAddress.getLocalHost().getHostName();
            rd = new NodeData(ip, name);
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }

        return rd;
    }
    /**
     * 判断是不是当前服务器是不是master服务器
     * @param rd
     * @return
     */
    public static boolean isMaster(NodeData rd) {
        try {
            NodeData masterData = zkClient.readData(MASTER_PATH, true);
            if (masterData.equals(rd)) {
                return true;
            }
            return false;
        } catch (Exception e) {
            return false;
        }

    }
    /**
     * 
     *获取master服务器的信息
     * @return
     */
    public static NodeData getMaster() {
        try {
            NodeData masterData = zkClient.readData(MASTER_PATH, true);
            return masterData;
        } catch (Exception e) {
            return null;
        }

    }
   /**
    * 服务器启动方法
    */
    public static void serverStartUp() {

        NodeData rd = getServerInfo();
        WorkServer workServer = new WorkServer(rd);
        workServer.setZkClient(zkClient);
        try {
            workServer.start();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

master选举方法:



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.ZkNodeExistsException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.zookeeper.CreateMode;
/**
 * 
 * @author kexiaolin
 * @date 2018-03-13
 * @description:master选举模式
 */
public class WorkServer {

    private Log log=LogFactory.getLog(getClass());
    private boolean isRunning=false;

    private NodeData masterData;//保存现在的master服务器是谁
    private NodeData serverData;//保存当前服务器信息
    private static final String MASTER_PATH="/master";
    private ZkClient zkClient;
    private NodeDataListener dataListener;

    private ScheduledExecutorService delayExector=Executors.newScheduledThreadPool(1);
    private int delayTime=5;
    public WorkServer(NodeData rd){
        this.serverData=rd;
        this.dataListener=new NodeDataListener();

    }
    public boolean stop() {
        if (!isRunning) {
            return false;
        }
        try {
            isRunning = false;
            delayExector.shutdown();
            zkClient.unsubscribeDataChanges(MASTER_PATH, dataListener);
            releseMaster();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        } 
    }

    public boolean start() {
        if (isRunning) {
            return false;
        }
        try {
            this.isRunning=true;
            zkClient.subscribeDataChanges(MASTER_PATH, dataListener);
            takeMaster();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }


    }
    private void takeMaster() {

        if (!isRunning) {
            return ;
        }
        try {
            zkClient.create(MASTER_PATH, serverData, CreateMode.EPHEMERAL);
            masterData=serverData;
            log.info("master server taked ==="+masterData);
        } catch (ZkNodeExistsException e) {
            NodeData rData=zkClient.readData(MASTER_PATH,true);
            log.info("master server old is  ==="+rData);
            if (rData==null) {
                 takeMaster();
            }else{

                masterData=rData;
            }
        } 
    }

    private void releseMaster(){

        if (checkMaster()) {
            zkClient.delete(MASTER_PATH);
        }
    }
    private boolean checkMaster() {

        try {
            NodeData rData=zkClient.readData(MASTER_PATH,true);
            masterData=rData;
            if (masterData.equals(serverData)) {
                return true;
            }return false;
        } catch (Exception e) {
            return false;
        }
    }
    private class NodeDataListener implements  IZkDataListener{

        @Override
        public void handleDataChange(String dataPath, Object data) throws Exception {

            log.info("master server is down ===="+masterData);
            //应对网络抖动的情况,可以防止网络波动导致master频繁切换 
            if (masterData.equals(serverData)) {//说明上一次选举的master是当前服务器
                  takeMaster(); 
            }else{//如果上一轮master不是当前服务器,延迟5秒再去争抢master
                delayExector.schedule(new Runnable() {
                    public void run() {
                        takeMaster();
                    }
                }, delayTime, TimeUnit.SECONDS);
            }

        }

        @Override
        public void handleDataDeleted(String dataPath) throws Exception {

        }

    };
    public ZkClient getZkClient() {
        return zkClient;
    }
    public void setZkClient(ZkClient zkClient) {
        this.zkClient = zkClient;
    }



    }

业务代码只能部分省略。。。

1.服务器启动的时候在listener里:

public class ServiceStartupListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent event) {
        /**
         * .......
          业务代码省略
           ........
         */
        //服务器在zookeeper注册
        ZookeeperUtil.serverStartUp();

    }

2.在定时任务处理时,先判断是不是master

public class JobTask extends QuartzJobBean {


    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {

        /**
         * 采用master选举模式,只有master服务器才运行该job
         * 1.保证单点故障时另一个服务器争抢到master权利,执行该job
         * 2.保证有且只有一个服务器运行该job
         */
        NodeData runningData=ZookeeperUtil.getServerInfo();
        boolean isMaster=ZookeeperUtil.isMaster(runningData);
        log.info("====is master=="+isMaster+",server:"+runningData+",master:"+ZookeeperUtil.getMaster());
        if (isMaster) {
         /**
             具体执行任务代码
            */
        }
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值