Zookeeper实现master-slave选举

Zookeeper实现master-slave选举

默认的选举规则称为:FastLeaderELection

2017-05-23 11:33 258人阅读 评论(0) 收藏 举报
Zookeeper实现master-slave选举 分类:

 场景描述

   比如存在一个webservice服务 该服务用于提供 一个获取uuid的服务  这个服务调用的程序不多 但是需要考虑到单点故障 当其中一台挂掉后 另一台机器需要充当master提供服务

 实线流程

   图解:

Zookeeper实现master-slave选举

1.zookeeper集群 (这里模拟 我只开启了一台主机 58.1

2. web服务器(两台服务器用于 master-salve模式)

 Web服务器就提供一个webservice服务 (jax-wx 必须jdk1.7支持)
[java] view plain copy
  1.         @WebService  
  2. public class UniqueString {  
  3.     @WebMethod  
  4.     public String get(){  
  5.         return UUID.randomUUID().toString();  
  6.     }  
  7.     public static void main(String[] args) throws Exception {  
  8.         //获取当前服务器的ip  
  9.         String curIp=getIp();  
  10.         //发布主应用 主webservice  
  11.         Endpoint.publish("http://"+curIp+":8801/getId"new UniqueString());  
  12.           
  13.     }  
  14. }  

web服务器(58.131,58.132都需要部署该程序)启动时 必须在zookeeper服务器上进行争抢注册/master节点

 创建为 /master=web服务器的ip】  该节点为临时节点

 假设 192.168.58.131争抢到了zookeeper上通过zkCli命令连接get /master查看/master的值就为192.168.58.131如果131挂了

 此时zookeeper131的连接中断session超时后 自动删除临时节点/master此时132监听到/master的数据删除事件 就需要开始争抢 

    master权注册/master=192.168.58.132

两台服务 需要处理的动作为

   启动服务器 开始争抢/master注册  监听/master的删除(又要开始争抢)

   代码如下(这里使用zkclient


[java] view plain copy
  1. package webserver;  
  2.   
  3. import java.net.InetAddress;  
  4. import java.net.NetworkInterface;  
  5. import java.net.SocketException;  
  6. import java.util.Enumeration;  
  7.   
  8. import org.I0Itec.zkclient.IZkDataListener;  
  9. import org.I0Itec.zkclient.ZkClient;  
  10. import org.I0Itec.zkclient.exception.ZkException;  
  11. import org.I0Itec.zkclient.exception.ZkInterruptedException;  
  12. import org.I0Itec.zkclient.exception.ZkNodeExistsException;  
  13. import org.apache.zookeeper.CreateMode;  
  14.   
  15. /** 
  16.  * 这里使用Master-Slave模式 使用一台主机(Master)作为对外提供服务的主机 
  17.  *                  其他服务器是备机(Slave ) 如果主机挂了 从机开始选举选择 
  18.  * 服务器的选举 
  19.  *  
  20.  * /master  --   master的ip地址 
  21.  * @author jiaozi 
  22.  * 
  23.  */  
  24. public class ServerSelector {  
  25.     private String zookkerUrl="192.168.58.1:2181";//zookeeper的连接地址  
  26.     private String masterNode="/master";//注册到zookeeper的节点名称   /master中存储的是服务器的ip地址  
  27.     private String curIp;               //当前的ip地址  
  28.     private String masterIp;            //master的ip地址  
  29.     private ZkClient zk=null;           //zookeeper客户端  
  30.     private IZkDataListener dataListener;//监听/master节点的数据变化  
  31.     public ServerSelector(String curIp){  
  32.         this.curIp=curIp;  
  33.     }  
  34.     public void start() throws Exception{  
  35.         //连接zookeeper 设置5秒连接超时   
  36.         //session超时时间越长 假如服务器挂了 等待session超时时间 临时节点才删除  
  37.         zk=new ZkClient(zookkerUrl,30001000);  
  38.         dataListener=new IZkDataListener() {  
  39.             /** 
  40.              * 如果数据被删除了 可能有master挂了 此时必须争抢master 
  41.              */  
  42.             @Override  
  43.             public void handleDataDeleted(String arg0) throws Exception {  
  44.                 fightMaster();  
  45.             }  
  46.             //数据被修改了  
  47.             @Override  
  48.             public void handleDataChange(String arg0, Object arg1) throws Exception {}  
  49.         };  
  50.         //订阅master节点的数据修改节点  
  51.         zk.subscribeDataChanges(masterNode,dataListener);  
  52.         fightMaster();  
  53.     }  
  54.     /** 
  55.      * 当前主机争夺master 
  56.      */  
  57.     public void fightMaster(){  
  58.         //尝试去创建master节点  有可能别的服务器抢到了 可能会出现异常  
  59.         try {  
  60.             Object mastIp=zk.create(masterNode,curIp,CreateMode.EPHEMERAL);  
  61.         } catch (Exception e) {  
  62.             //节点已存在  
  63.             if(e instanceof ZkNodeExistsException){  
  64.                 //由于不同的原因(处理过程 master挂了)导致没取到  重新争抢 直到找到master  
  65.                 Object masterIpObj=zk.readData(masterNode);  
  66.                 if(masterIpObj==null){  
  67.                     fightMaster();  
  68.                 }else{  
  69.                     masterIp=masterIpObj.toString();  
  70.                 }  
  71.             }  
  72.         }   
  73.           
  74.     }  
  75.       
  76.       
  77. }  

修改web服务器启动的代码 启动服务后 开始选举


[java] view plain copy
  1. package webserver;  
  2.   
  3. import java.net.InetAddress;  
  4. import java.net.NetworkInterface;  
  5. import java.net.SocketException;  
  6. import java.util.Enumeration;  
  7. import java.util.UUID;  
  8.   
  9. import javax.jws.WebMethod;  
  10. import javax.jws.WebService;  
  11. import javax.xml.ws.Endpoint;  
  12.   
  13. @WebService  
  14. public class UniqueString {  
  15.     @WebMethod  
  16.     public String get(){  
  17.         return UUID.randomUUID().toString();  
  18.     }  
  19.     //zookeeper选举类  
  20.     static ServerSelector ss=null;  
  21.     public static void main(String[] args) throws Exception {  
  22.         //获取当前服务器的ip  
  23.         String curIp=getIp();  
  24.         //发布主应用 主webservice  
  25.         Endpoint.publish("http://"+curIp+":8801/getId"new UniqueString());  
  26.         //开始选举进程 传入当前ip  
  27.         ss=new ServerSelector(curIp);  
  28.         ss.start();  
  29.     }  
  30.     /** 
  31.      * 获取当前主机ip地址 
  32.      * 这里我挂的虚拟机 和window主机 ip都是58段 
  33.      * @return 
  34.      * @throws SocketException 
  35.      */  
  36.     public static String getIp() throws SocketException{  
  37.         Enumeration<NetworkInterface> interfs = NetworkInterface.getNetworkInterfaces();    
  38.         while (interfs.hasMoreElements())    
  39.         {    
  40.             NetworkInterface interf = interfs.nextElement();    
  41.             Enumeration<InetAddress> addres = interf.getInetAddresses();    
  42.             while (addres.hasMoreElements())    
  43.             {    
  44.                 InetAddress in = addres.nextElement();    
  45.                 if (in.getHostAddress().startsWith("192.168.58"))    
  46.                 {    
  47.                     return in.getHostAddress();   
  48.                 }    
  49.             }    
  50.         }    
  51.         return null;  
  52.     }  
  53. }  
服务器代码编写完成 后  eclipse导出为jar包  选择mainclass为UniqueString

将需要用到的jar包 放在lib目录下 

Zookeeper实现master-slave选举

上传到 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节点的值  

   Zookeeper实现master-slave选举

此时 看到 /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生成的代码

Zookeeper实现master-slave选举
[java] view plain copy
  1. package webclient;  
  2.   
  3. import java.rmi.RemoteException;  
  4.   
  5. import javax.xml.rpc.ServiceException;  
  6.   
  7. import org.I0Itec.zkclient.IZkDataListener;  
  8. import org.I0Itec.zkclient.ZkClient;  
  9. import org.I0Itec.zkclient.exception.ZkNoNodeException;  
  10. import org.apache.zookeeper.ZooKeeper;  
  11. /** 
  12.  * 客户端模拟根据zookeeper获取到master 
  13.  *   调用webservice获取id 
  14.  * @author jiaozi 
  15.  * 
  16.  */  
  17. public class Test {  
  18.     static String masterNode="/master";//master节点  
  19.     static String masterIp=null;// 获取master的ip地址  
  20.     static ZkClient zk=null;  
  21.     static String zookkerUrl="192.168.58.1:2181";//zookeeper的连接地址  
  22.     /** 
  23.      * 获取master的ip 
  24.      * @return 
  25.      * @throws InterruptedException 
  26.      */  
  27.     public static String getMasterIp() throws InterruptedException{  
  28.         Object ipObj;  
  29.         try {  
  30.             //读取master节点的ip值  
  31.             ipObj = zk.readData(masterNode);  
  32.         } catch (ZkNoNodeException e) {  
  33.             //如果节点不存在 休眠10s 继续读取 直到读取到为止  
  34.             Thread.sleep(10);  
  35.             return getMasterIp();  
  36.         }  
  37.         //如果没有获取到ip 也继续去读取  
  38.         if(ipObj==null){  
  39.             Thread.sleep(10);  
  40.             return getMasterIp();  
  41.         }  
  42.         return ipObj.toString();  
  43.     }  
  44.     public static void main(String[] args) throws Exception {   
  45.         zk=new ZkClient(zookkerUrl,30001000);  
  46.         //客户端调用时获取一次master的ip 以后就使用该ip缓存 监听zookeeper的数据变化  
  47.         System.out.println("尝试获取master");  
  48.         masterIp=getMasterIp();  
  49.         //监听master节点数据的变化  
  50.         zk.subscribeDataChanges(masterNode, new IZkDataListener() {  
  51.             @Override  
  52.             public void handleDataDeleted(String arg0) throws Exception {  
  53.             }  
  54.               
  55.             @Override  
  56.             public void handleDataChange(String arg0, Object arg1) throws Exception {  
  57.                 masterIp=getMasterIp();  
  58.             }  
  59.         });  
  60.         //每隔5s 循环调用webservice  看down掉其中任何一台服务器是否都可以获取到存在的服务器 并且连接webservice  
  61.         while(true){  
  62.             System.out.println("获取到的masterip是:"+masterIp);  
  63.             String wsdlUrl="http://"+masterIp+":8801/getId";  
  64.               
  65.             try {  
  66.                 UniqueStringServiceLocator u=new UniqueStringServiceLocator();  
  67.                 u.setUniqueStringPortEndpointAddress(wsdlUrl);  
  68.                 String id=u.getUniqueStringPort().get();  
  69.                 System.out.println(id);  
  70.             } catch (Exception e) {  
  71.                 System.out.println("webservice无法连接");  
  72.             }  
  73.             Thread.sleep(5000);  
  74.         }  
  75.     }  
  76.   
  77. }  

这里有时还需要考虑个问题 就是  服务器  58.131和58.132中的master和服务器之间出现了网络的不稳定 此时和服务器之间因为超时 导致 master节点从zookeeper服务干掉了  但是 master对应的服务器 并没有真正挂掉  有可能一些对象的初始化在新服务器需要重新 处理 需要消耗资源  所以 可以让之前的master优先去抢  让之前不是master的节点 过几秒后再去抢  红色的部分为修改代码

[java] view plain copy
  1. package webserver;  
  2.   
  3. import java.util.concurrent.Executors;  
  4. import java.util.concurrent.ScheduledExecutorService;  
  5. import java.util.concurrent.TimeUnit;  
  6.   
  7. import org.I0Itec.zkclient.IZkDataListener;  
  8. import org.I0Itec.zkclient.ZkClient;  
  9. import org.I0Itec.zkclient.exception.ZkNodeExistsException;  
  10. import org.apache.zookeeper.CreateMode;  
  11.   
  12. /** 
  13.  * 这里使用Master-Slave模式 使用一台主机(Master)作为对外提供服务的主机 
  14.  *                  其他服务器是备机(Slave ) 如果主机挂了 从机开始选举选择 
  15.  * 服务器的选举 
  16.  *  
  17.  * /master  --   master的ip地址 
  18.  * @author jiaozi 
  19.  * 
  20.  */  
  21. public class ServerSelector {  
  22.     private String zookkerUrl="192.168.58.1:2181";//zookeeper的连接地址  
  23.     private String masterNode="/master";//注册到zookeeper的节点名称   /master中存储的是服务器的ip地址  
  24.     private String curIp;               //当前的ip地址  
  25.     private String masterIp;            //master的ip地址  
  26.     private ZkClient zk=null;           //zookeeper客户端  
  27.     private IZkDataListener dataListener;//监听/master节点的数据变化  
  28.     public ServerSelector(String curIp){  
  29.         this.curIp=curIp;  
  30.     }  
  31.     public void start() throws Exception{  
  32.         //连接zookeeper 设置5秒连接超时   
  33.         //session超时时间越长 假如服务器挂了 等待session超时时间 临时节点才删除  
  34.         zk=new ZkClient(zookkerUrl,30001000);  
  35.         dataListener=new IZkDataListener() {  
  36.             /** 
  37.              * 如果数据被删除了 可能有master挂了 此时必须争抢master 
  38.              */  
  39.             @Override  
  40.             public void handleDataDeleted(String arg0) throws Exception {  
  41.                 <span style="color:#ff0000;">ScheduledExecutorService ses=Executors.newScheduledThreadPool(1);//这里线程池建议定义在全局属性 我这里为了方便标色  
  42.                 //如果当前ip和之前的masterip是一样的直接开抢   
  43.                 if(curIp.equals(masterIp)){  
  44.                     fightMaster();  
  45.                 }else{  
  46.                     //ip和master不一样 等待5秒后开抢 让之前的ip先抢 因为master没挂  
  47.                     ses.schedule(new Runnable() {  
  48.                           
  49.                         @Override  
  50.                         public void run() {  
  51.                             fightMaster();  
  52.                         }  
  53.                     }, 5, TimeUnit.SECONDS);  
  54.                 }</span>  
  55.             }  
  56.             //数据被修改了  
  57.             @Override  
  58.             public void handleDataChange(String arg0, Object arg1) throws Exception {}  
  59.         };  
  60.         //订阅master节点的数据修改节点  
  61.         zk.subscribeDataChanges(masterNode,dataListener);  
  62.         fightMaster();  
  63.     }  
  64.     /** 
  65.      * 当前主机争夺master 
  66.      */  
  67.     public void fightMaster(){  
  68.         //尝试去创建master节点  有可能别的服务器抢到了 可能会出现异常  
  69.         try {  
  70.             Object mastIp=zk.create(masterNode,curIp,CreateMode.EPHEMERAL);  
  71.         } catch (Exception e) {  
  72.             //节点已存在  
  73.             if(e instanceof ZkNodeExistsException){  
  74.                 //由于不同的原因(处理过程 master挂了)导致没取到  重新争抢 直到找到master  
  75.                 Object masterIpObj=zk.readData(masterNode);  
  76.                 if(masterIpObj==null){  
  77.                     fightMaster();  
  78.                 }else{  
  79.                     masterIp=masterIpObj.toString();  
  80.                 }  
  81.             }  
  82.         }   
  83.           
  84.     }  
  85.       
  86.       
  87. }  

最终运行结果:

 启动 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