学习了一些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) {
/**
具体执行任务代码
*/
}
}
}