一 场景描述
比如存在一个webservice服务 该服务用于提供 一个获取uuid的服务 这个服务调用的程序不多 但是需要考虑到单点故障 当其中一台挂掉后
另一台机器需要充当master提供服务
二 实线流程
图解:
1.zookeeper集群 (这里模拟 我只开启了一台主机 58.1)
2. web服务器(两台服务器用于 master-salve模式)
Web服务器就提供一个webservice服务 (jax-wx 必须jdk1.7支持)
-
@WebService
-
public class UniqueString {
-
@WebMethod
-
public String get(){
-
return UUID.randomUUID().toString();
-
}
-
public static void main(String[] args) throws Exception {
-
//获取当前服务器的ip
-
String curIp=getIp();
-
//发布主应用 主webservice
-
Endpoint.publish("http://"+curIp+":8801/getId", new UniqueString());
-
-
}
-
}
当web服务器(58.131,58.132都需要部署该程序)启动时 必须在zookeeper服务器上进行争抢注册/master节点
创建为 /master=【web服务器的ip】
该节点为临时节点
假设 192.168.58.131争抢到了zookeeper上通过zkCli命令连接get
/master查看/master的值就为192.168.58.131如果131挂了
此时zookeeper和131的连接中断session超时后
自动删除临时节点/master此时132监听到/master的数据删除事件
就需要开始争抢
master权注册/master=192.168.58.132
两台服务 需要处理的动作为
启动服务器 开始争抢/master注册 监听/master的删除(又要开始争抢)
代码如下(这里使用zkclient)
-
package webserver;
-
-
import java.net.InetAddress;
-
import java.net.NetworkInterface;
-
import java.net.SocketException;
-
import java.util.Enumeration;
-
-
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.ZkNodeExistsException;
-
import org.apache.zookeeper.CreateMode;
-
-
/**
-
* 这里使用Master-Slave模式 使用一台主机(Master)作为对外提供服务的主机
-
* 其他服务器是备机(Slave ) 如果主机挂了 从机开始选举选择
-
* 服务器的选举
-
*
-
* /master -- master的ip地址
-
* @author jiaozi
-
*
-
*/
-
public class ServerSelector {
-
private String zookkerUrl="192.168.58.1:2181";//zookeeper的连接地址
-
private String masterNode="/master";//注册到zookeeper的节点名称 /master中存储的是服务器的ip地址
-
private String curIp; //当前的ip地址
-
private String masterIp; //master的ip地址
-
private ZkClient zk=null; //zookeeper客户端
-
private IZkDataListener dataListener;//监听/master节点的数据变化
-
public ServerSelector(String curIp){
-
this.curIp=curIp;
-
}
-
public void start() throws Exception{
-
//连接zookeeper 设置5秒连接超时
-
//session超时时间越长 假如服务器挂了 等待session超时时间 临时节点才删除
-
zk=new ZkClient(zookkerUrl,3000, 1000);
-
dataListener=new IZkDataListener() {
-
/**
-
* 如果数据被删除了 可能有master挂了 此时必须争抢master
-
*/
-
@Override
-
public void handleDataDeleted(String arg0) throws Exception {
-
fightMaster();
-
}
-
//数据被修改了
-
@Override
-
public void handleDataChange(String arg0, Object arg1) throws Exception {}
-
};
-
//订阅master节点的数据修改节点
-
zk.subscribeDataChanges(masterNode,dataListener);
-
fightMaster();
-
}
-
/**
-
* 当前主机争夺master
-
*/
-
public void fightMaster(){
-
//尝试去创建master节点 有可能别的服务器抢到了 可能会出现异常
-
try {
-
Object mastIp=zk.create(masterNode,curIp,CreateMode.EPHEMERAL);
-
} catch (Exception e) {
-
//节点已存在
-
if(e instanceof ZkNodeExistsException){
-
//由于不同的原因(处理过程 master挂了)导致没取到 重新争抢 直到找到master
-
Object masterIpObj=zk.readData(masterNode);
-
if(masterIpObj==null){
-
fightMaster();
-
}else{
-
masterIp=masterIpObj.toString();
-
}
-
}
-
}
-
-
}
-
-
-
}
修改web服务器启动的代码 启动服务后 开始选举
-
package webserver;
-
-
import java.net.InetAddress;
-
import java.net.NetworkInterface;
-
import java.net.SocketException;
-
import java.util.Enumeration;
-
import java.util.UUID;
-
-
import javax.jws.WebMethod;
-
import javax.jws.WebService;
-
import javax.xml.ws.Endpoint;
-
-
@WebService
-
public class UniqueString {
-
@WebMethod
-
public String get(){
-
return UUID.randomUUID().toString();
-
}
-
//zookeeper选举类
-
static ServerSelector ss=null;
-
public static void main(String[] args) throws Exception {
-
//获取当前服务器的ip
-
String curIp=getIp();
-
//发布主应用 主webservice
-
Endpoint.publish("http://"+curIp+":8801/getId", new UniqueString());
-
//开始选举进程 传入当前ip
-
ss=new ServerSelector(curIp);
-
ss.start();
-
}
-
/**
-
* 获取当前主机ip地址
-
* 这里我挂的虚拟机 和window主机 ip都是58段
-
* @return
-
* @throws SocketException
-
*/
-
public static String getIp() throws SocketException{
-
Enumeration<NetworkInterface> interfs = NetworkInterface.getNetworkInterfaces();
-
while (interfs.hasMoreElements())
-
{
-
NetworkInterface interf = interfs.nextElement();
-
Enumeration<InetAddress> addres = interf.getInetAddresses();
-
while (addres.hasMoreElements())
-
{
-
InetAddress in = addres.nextElement();
-
if (in.getHostAddress().startsWith("192.168.58"))
-
{
-
return in.getHostAddress();
-
}
-
}
-
}
-
return null;
-
}
-
}
服务器代码编写完成 后 eclipse导出为jar包 选择mainclass为UniqueString将需要用到的jar包 放在lib目录下
上传到 58.131和58.132后 使用命令
nohup java -Djava.ext.dirs=./lib -jar webserver.jar 查看目录下的nohup.out文件查看是否启动
也可以通过 ps-ef | grep java 查看启动的java程序 成功后 通过客户端
zkCli.sh -server 192.168.58.1 查看 /master节点的值
此时 看到 /master=58.132
登录 58.132 关闭 服务 ps -ef | grep java 找到进程 kill 进程编号
通过客户端查看是否 /master=58.131 如果是 则表示服务器master-slave实现成功
2. 客户端(客户端需要调用webservice )
客户端调用之前 获取/master节点的服务器 通过服务器ip调用webservice
同时需要监听zookeeper节点的数据变化事件 需要重新替换新的服务器ip
这里调用webservice的代码(图中选中的)是通过eclipse上新建webservice
client生成的代码
-
package webclient;
-
-
import java.rmi.RemoteException;
-
-
import javax.xml.rpc.ServiceException;
-
-
import org.I0Itec.zkclient.IZkDataListener;
-
import org.I0Itec.zkclient.ZkClient;
-
import org.I0Itec.zkclient.exception.ZkNoNodeException;
-
import org.apache.zookeeper.ZooKeeper;
-
/**
-
* 客户端模拟根据zookeeper获取到master
-
* 调用webservice获取id
-
* @author jiaozi
-
*
-
*/
-
public class Test {
-
static String masterNode="/master";//master节点
-
static String masterIp=null;// 获取master的ip地址
-
static ZkClient zk=null;
-
static String zookkerUrl="192.168.58.1:2181";//zookeeper的连接地址
-
/**
-
* 获取master的ip
-
* @return
-
* @throws InterruptedException
-
*/
-
public static String getMasterIp() throws InterruptedException{
-
Object ipObj;
-
try {
-
//读取master节点的ip值
-
ipObj = zk.readData(masterNode);
-
} catch (ZkNoNodeException e) {
-
//如果节点不存在 休眠10s 继续读取 直到读取到为止
-
Thread.sleep(10);
-
return getMasterIp();
-
}
-
//如果没有获取到ip 也继续去读取
-
if(ipObj==null){
-
Thread.sleep(10);
-
return getMasterIp();
-
}
-
return ipObj.toString();
-
}
-
public static void main(String[] args) throws Exception {
-
zk=new ZkClient(zookkerUrl,3000, 1000);
-
//客户端调用时获取一次master的ip 以后就使用该ip缓存 监听zookeeper的数据变化
-
System.out.println("尝试获取master");
-
masterIp=getMasterIp();
-
//监听master节点数据的变化
-
zk.subscribeDataChanges(masterNode, new IZkDataListener() {
-
@Override
-
public void handleDataDeleted(String arg0) throws Exception {
-
}
-
-
@Override
-
public void handleDataChange(String arg0, Object arg1) throws Exception {
-
masterIp=getMasterIp();
-
}
-
});
-
//每隔5s 循环调用webservice 看down掉其中任何一台服务器是否都可以获取到存在的服务器 并且连接webservice
-
while(true){
-
System.out.println("获取到的masterip是:"+masterIp);
-
String wsdlUrl="http://"+masterIp+":8801/getId";
-
-
try {
-
UniqueStringServiceLocator u=new UniqueStringServiceLocator();
-
u.setUniqueStringPortEndpointAddress(wsdlUrl);
-
String id=u.getUniqueStringPort().get();
-
System.out.println(id);
-
} catch (Exception e) {
-
System.out.println("webservice无法连接");
-
}
-
Thread.sleep(5000);
-
}
-
}
-
-
}
这里有时还需要考虑个问题 就是 服务器 58.131和58.132中的master和服务器之间出现了网络的不稳定 此时和服务器之间因为超时 导致 master节点从zookeeper服务干掉了 但是 master对应的服务器 并没有真正挂掉 有可能一些对象的初始化在新服务器需要重新 处理 需要消耗资源 所以 可以让之前的master优先去抢 让之前不是master的节点 过几秒后再去抢 红色的部分为修改代码
-
package webserver;
-
-
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.zookeeper.CreateMode;
-
-
/**
-
* 这里使用Master-Slave模式 使用一台主机(Master)作为对外提供服务的主机
-
* 其他服务器是备机(Slave ) 如果主机挂了 从机开始选举选择
-
* 服务器的选举
-
*
-
* /master -- master的ip地址
-
* @author jiaozi
-
*
-
*/
-
public class ServerSelector {
-
private String zookkerUrl="192.168.58.1:2181";//zookeeper的连接地址
-
private String masterNode="/master";//注册到zookeeper的节点名称 /master中存储的是服务器的ip地址
-
private String curIp; //当前的ip地址
-
private String masterIp; //master的ip地址
-
private ZkClient zk=null; //zookeeper客户端
-
private IZkDataListener dataListener;//监听/master节点的数据变化
-
public ServerSelector(String curIp){
-
this.curIp=curIp;
-
}
-
public void start() throws Exception{
-
//连接zookeeper 设置5秒连接超时
-
//session超时时间越长 假如服务器挂了 等待session超时时间 临时节点才删除
-
zk=new ZkClient(zookkerUrl,3000, 1000);
-
dataListener=new IZkDataListener() {
-
/**
-
* 如果数据被删除了 可能有master挂了 此时必须争抢master
-
*/
-
@Override
-
public void handleDataDeleted(String arg0) throws Exception {
-
<span style="color:#ff0000;">ScheduledExecutorService ses=Executors.newScheduledThreadPool(1);//这里线程池建议定义在全局属性 我这里为了方便标色
-
//如果当前ip和之前的masterip是一样的直接开抢
-
if(curIp.equals(masterIp)){
-
fightMaster();
-
}else{
-
//ip和master不一样 等待5秒后开抢 让之前的ip先抢 因为master没挂
-
ses.schedule(new Runnable() {
-
-
@Override
-
public void run() {
-
fightMaster();
-
}
-
}, 5, TimeUnit.SECONDS);
-
}</span>
-
}
-
//数据被修改了
-
@Override
-
public void handleDataChange(String arg0, Object arg1) throws Exception {}
-
};
-
//订阅master节点的数据修改节点
-
zk.subscribeDataChanges(masterNode,dataListener);
-
fightMaster();
-
}
-
/**
-
* 当前主机争夺master
-
*/
-
public void fightMaster(){
-
//尝试去创建master节点 有可能别的服务器抢到了 可能会出现异常
-
try {
-
Object mastIp=zk.create(masterNode,curIp,CreateMode.EPHEMERAL);
-
} catch (Exception e) {
-
//节点已存在
-
if(e instanceof ZkNodeExistsException){
-
//由于不同的原因(处理过程 master挂了)导致没取到 重新争抢 直到找到master
-
Object masterIpObj=zk.readData(masterNode);
-
if(masterIpObj==null){
-
fightMaster();
-
}else{
-
masterIp=masterIpObj.toString();
-
}
-
}
-
}
-
-
}
-
-
-
}
最终运行结果:
启动 58.131和58.132后 运行客户端 main 运行一会后停掉 master 58.132进程 发现自动开始调用 58.131的web服务了
获取到的masterip是:192.168.58.132
eb5743c3-d7f0-4265-97e4-05d21531b062
获取到的masterip是:192.168.58.132
843638ae-e7d1-43e3-ba3f-dab2916c7e77
获取到的masterip是:192.168.58.132
webservice无法连接
获取到的masterip是:192.168.58.131
7ee85b77-83f6-430e-8df3-cfd785e3bea1
获取到的masterip是:192.168.58.131
8fb278d1-0b6f-4b35-bbeb-1fa66e145c6c