对于zookeeper这种东西,仅仅知道怎么安装是远远不够的,至少要对其几个典型的应用场景进行了解,才能比较全面的知道zk究竟能干啥,怎么玩儿,以后的日子里才能知道这货如何能为我所用。于是,有了如下的学习:

我们知道zookeeper可以用于搭建高可用服务框架,主要先看以下几个应用场景:
1、 master的选举基本思路和编码实现
2、 数据的发布和订阅
3、 软负载均衡
4、 分布式队列
5、 分布式锁
6、 命名服务

目前zookeeper常用的开发包有zkclient跟curator,后者更为方便,日常开发使用较多。

master选举

1、使用场景及结构

  现在很多时候我们的服务需要7*24小时工作,假如一台机器挂了,我们希望能有其它机器顶替它继续工作。此类问题现在多采用master-
salve模式,也就是常说的主从模式,正常情况下主机提供服务,备机负责监听主机状态,当主机异常时,可以自动切换到备机继续提供服务(这里有点儿类似
于数据库主库跟备库,备机正常情况下只监听,不工作),这个切换过程中选出下一个主机的过程就是master选举。

  对于以上提到的场景,传统的解决方式是采用一个备用节点,这个备用节点定期给当前主节点发送ping包,主节点收到ping包后会向备用节点发
送应答ack,当备用节点收到应答,就认为主节点还活着,让它继续提供服务,否则就认为主节点挂掉了,自己将开始行使主节点职责。如图1所示:

    

                  图1

   但这种方式会存在一个隐患,就是网络故障问题。看一下图2:

    

          图2

   也就是说,我们的主节点并没有挂掉,只是在备用节点
ping主节点,请求应答的时候发生网络故障,这样我们的备用节点同样收不到应答,就会认为主节点挂掉,然后备机会启动自己的master实例。这样就会
导致系统中有两个主节点,也就是双master。出现双master以后,我们的从节点会将它做的事情一部分汇报给主节点,一部分汇报给备用节点,这样服
务就乱套了。为了防止这种情况出现,我们可以考虑采用zookeeper,虽然它不能阻止网络故障的出现,但它能保证同一时刻系统中只存在一个主节点。我
们来看zookeeper是怎么实现的:

  在
此处,抢主程序是包含在服务程序中,需要程序员来手动写抢主逻辑的,比如当当开源框架elastic-job中,就有关于选主的部分,参
见:elastic-job-core/main/java/com/dangdang/ddframe/job/internal/election文
件夹下的选主代码。

  一点额外的话:zookeeper自己在集群环境下的抢
主算法有三种,可以通过配置文件来设定,默认采用FastLeaderElection,不作赘述;此处主要讨论集群环境中,应用程序利用master的
特点,自己选主的过程。程序自己选主,每个人都有自己的一套算法,有采用“最小编号”的,有采用类似“多数投票”的,各有优劣,本文的算法仅作演示理解使
用:

  结构图:

  结构图解释:左侧树状结构为zookeeper集群,右侧为程序服务器。所有的服务器在启动的时候,都会订阅zookeeper中master
节点的删除事件,以便在主服务器挂掉的时候进行抢主操作;所有服务器同时会在servers节点下注册一个临时节点(保存自己的基本信息),以便于应用程
序读取当前可用的服务器列表。

  选主原理介绍:zookeeper的节点有两种类型,持久节点跟临时节点。临时节点有个特性,就是如果注册这个节点的机器失去连接(通常是宕
机),那么这个节点会被zookeeper删除。选主过程就是利用这个特性,在服务器启动的时候,去zookeeper特定的一个目录下注册一个临时节点
(这个节点作为master,谁注册了这个节点谁就是master),注册的时候,如果发现该节点已经存在,则说明已经有别的服务器注册了(也就是有别的
服务器已经抢主成功),那么当前服务器只能放弃抢主,作为从机存在。同时,抢主失败的当前服务器需要订阅该临时节点的删除事件,以便该节点删除时(也就是
注册该节点的服务器宕机了或者网络断了之类的)进行再次抢主操作。从机具体需要去哪里注册服务器列表的临时节点,节点保存什么信息,根据具体的业务不同自
行约定。选主的过程,其实就是简单的争抢在zookeeper注册临时节点的操作,谁注册了约定的临时节点,谁就是master。

  ps:本文的例子中,并未用到结构图server节点下的数据。但换一种算法或者业务场景就会用到,算法比如提到的最小编号,主要逻辑是主节点
挂掉后,从节点里边编号最小的成为主节点,此时会用到该节点内容。换一种业务场景:集群环境中,有很多任务要处理, 主节点负责接收任务,并根据一定算法
将任务分配到不同的机器上执行;这种情况下,主节点跟从节点的职责也是不同的,主节点挂掉也会涉及到从节点进行master选举的问题。这种情况下,很显
然,作为主节点需要知道当前有多少个从节点还活着,那么此时也会需要用到servers节点下的数据了。

2、编码实现

  主要有两个类,WorkServer为主服务类,RunningData用于记录运行数据。因为是简单的demo,我们只做抢master节点的编码,对于从节点应该去哪里注册服务列表信息,不作编码。

  采用zkClient实现,代码如下:

  WorkServer类:

  

  1 package mastersalve;
  2
  3 import org.I0Itec.zkclient.IZkDataListener;
  4 import org.I0Itec.zkclient.ZkClient;
  5 import org.I0Itec.zkclient.exception.ZkInterruptedException;
  6 import org.I0Itec.zkclient.exception.ZkNoNodeException;
  7 import org.I0Itec.zkclient.exception.ZkNodeExistsException;
  8 import org.apache.zookeeper.CreateMode;
  9
 10 import java.util.concurrent.Executors;
 11 import java.util.concurrent.ScheduledExecutorService;
 12 import java.util.concurrent.TimeUnit;
 13
 14 /**
 15  * Created by nevermore on 16/6/22.
 16  */
 17 public class WorkServer {
 18
 19     //客户端状态
 20     private volatile boolean running = false;
 21
 22     private ZkClient zkClient;
 23
 24     //zk主节点路径
 25     public static final String MASTER_PATH = "/master";
 26
 27     //监听(用于监听主节点删除事件)
 28     private IZkDataListener dataListener;
 29
 30     //服务器基本信息
 31     private RunningData serverData;
 32     //主节点基本信息
 33     private RunningData masterData;
 34
 35     //调度器
 36     private ScheduledExecutorService delayExector = Executors.newScheduledThreadPool(1);
 37     //延迟时间5s
 38     private int delayTime = 5;
 39
 40
 41
 42     public WorkServer(RunningData runningData){
 43         this.serverData = runningData;
 44         this.dataListener = new IZkDataListener() {
 45             @Override
 46             public void handleDataChange(String s, Object o) throws Exception {
 47
 48             }
 49
 50             @Override
 51             public void handleDataDeleted(String s) throws Exception {
 52                 //takeMaster();
 53
 54                 if(masterData != null && masterData.getName().equals(serverData.getName())){//若之前master为本机,则立即抢主,否则延迟5秒抢主(防止小故障引起的抢主可能导致的网络数据风暴)
 55                     takeMaster();
 56                 }else{
 57                     delayExector.schedule(new Runnable() {
 58                         @Override
 59                         public void run() {
 60                             takeMaster();
 61                         }
 62                     },delayTime, TimeUnit.SECONDS);
 63                 }
 64
 65             }
 66         };
 67     }
 68
 69     //启动
 70     public void start() throws Exception{
 71         if(running){
 72             throw new Exception("server has startup....");
 73         }
 74         running = true;
 75         zkClient.subscribeDataChanges(MASTER_PATH,dataListener);
 76         takeMaster();
 77     }
 78
 79     //停止
 80     public void stop() throws Exception{
 81         if(!running){
 82             throw new Exception("server has stopped.....");
 83         }
 84         running = false;
 85         delayExector.shutdown();
 86         zkClient.unsubscribeDataChanges(MASTER_PATH,dataListener);
 87         releaseMaster();
 88     }
 89
 90     //抢注主节点
 91     private void takeMaster(){
 92         if(!running) return ;
 93
 94         try {
 95             zkClient.create(MASTER_PATH, serverData, CreateMode.EPHEMERAL);
 96             masterData = serverData;
 97             System.out.println(serverData.getName()+" is master");
 98
 99             delayExector.schedule(new Runnable() {//测试抢主用,每5s释放一次主节点
100                 @Override
101                 public void run() {
102                     if(checkMaster()){
103                         releaseMaster();
104                     }
105                 }
106             },5,TimeUnit.SECONDS);
107
108
109         }catch (ZkNodeExistsException e){//节点已存在
110             RunningData runningData = zkClient.readData(MASTER_PATH,true);
111             if(runningData == null){//读取主节点时,主节点被释放
112                 takeMaster();
113             }else{
114                 masterData = runningData;
115             }
116         } catch (Exception e) {
117             // ignore;
118         }
119
120     }
121     //释放主节点
122     private void releaseMaster(){
123         if(checkMaster()){
124             zkClient.delete(MASTER_PATH);
125         }
126     }
127     //检验自己是否是主节点
128     private boolean checkMaster(){
129         try {
130             RunningData runningData = zkClient.readData(MASTER_PATH);
131             masterData = runningData;
132             if (masterData.getName().equals(serverData.getName())) {
133                 return true;
134             }
135             return false;
136
137         }catch (ZkNoNodeException e){//节点不存在
138             return  false;
139         }catch (ZkInterruptedException e){//网络中断
140             return checkMaster();
141         }catch (Exception e){//其它
142             return false;
143         }
144     }
145
146     public void setZkClient(ZkClient zkClient) {
147         this.zkClient = zkClient;
148     }
149
150     public ZkClient getZkClient() {
151         return zkClient;
152     }
153 }

  RunningData类:

package mastersalve;

import java.io.Serializable;

/**
 * Created by nevermore on 16/6/22.
 */
public class RunningData implements Serializable {

    private static final long serialVersionUID = 4260577459043203630L;

    //服务器id
    private long cid;
    //服务器名称
    private String name;

    public long getCid() {
        return cid;
    }

    public void setCid(long cid) {
        this.cid = cid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

  说明:在实际生产环境中,可能会由于插拔网线等导致网络短时的不稳定,也就是网络抖动。由于正式生产环境中可能server在zk上注册的信 息是比较多的,而且server的数量也是比较多的,那么每一次切换主机,每台server要同步的数据量(比如要获取谁是master,当前有哪些 salve等信息,具体视业务不同而定)也是比较大的。那么我们希望,这种短时间的网络抖动最好不要影响我们的系统稳定,也就是最好选出来的master 还是原来的机器,那么就可以避免发现master更换后,各个salve因为要同步数据等导致的zk数据网络风暴。所以在WorkServer 中,54-63行,我们抢主的时候,如果之前主机是本机,则立即抢主,否则延迟5s抢主。这样就给原来主机预留出一定时间让其在新一轮选主中占据优势,从 而利于环境稳定。

  测试代码:

 1 package mastersalve;
 2
 3 import org.I0Itec.zkclient.ZkClient;
 4 import org.I0Itec.zkclient.serialize.SerializableSerializer;
 5
 6 import java.io.BufferedReader;
 7 import java.io.InputStreamReader;
 8 import java.util.ArrayList;
 9 import java.util.List;
10
11 /**
12  * Created by nevermore on 16/6/23.
13  */
14 public class LeaderSelectorZkClient {
15
16     //启动的服务个数
17     private static final int        CLIENT_QTY = 10;
18     //zookeeper服务器的地址
19     private static final String     ZOOKEEPER_SERVER = "localhost:2181";
20
21
22     public static void main(String[] args) throws Exception{
23         //保存所有zkClient的列表
24         List<ZkClient> clients = new ArrayList<ZkClient>();
25         //保存所有服务的列表
26         List<WorkServer>  workServers = new ArrayList<WorkServer>();
27
28         try{
29             for ( int i = 0; i < CLIENT_QTY; ++i ){
30                 //创建zkClient
31                 ZkClient client = new ZkClient(ZOOKEEPER_SERVER, 5000, 5000, new SerializableSerializer());
32                 clients.add(client);
33                 //创建serverData
34                 RunningData runningData = new RunningData();
35                 runningData.setCid(Long.valueOf(i));
36                 runningData.setName("Client #" + i);
37                 //创建服务
38                 WorkServer  workServer = new WorkServer(runningData);
39                 workServer.setZkClient(client);
40
41                 workServers.add(workServer);
42                 workServer.start();
43             }
44
45             System.out.println("敲回车键退出!\n");
46             new BufferedReader(new InputStreamReader(System.in)).readLine();
47         }finally{
48             System.out.println("Shutting down...");
49
50             for ( WorkServer workServer : workServers ){
51                 try {
52                     workServer.stop();
53                 } catch (Exception e) {
54                     e.printStackTrace();
55                 }
56             }
57             for ( ZkClient client : clients ){
58                 try {
59                     client.close();
60                 } catch (Exception e) {
61                     e.printStackTrace();
62                 }
63             }
64         }
65     }
66 }

  两次测试,本地模拟10台server,分别不启用防止网络抖动跟启动防抖动两次测试结果如下:

  未启动防抖动:

  

  启用防抖动:

  

  可以看到,未启用的时候,断线后重新选出的主机是随机的,没规律;启用防抖动后,每次选出的master都是id为0的机器。

-----------------------------------------------------------------------------------------------------------------------------

  至此,我们已经通过编码实现了简单的master选举。但是,不知你有没有发现,,,,这个选主过程的代码还真是麻烦啊!

  我们只是做一个demo,其中并未考虑复杂的业务场景,但其中的  监听,异常  等代码的处理还是让我觉得有些头大,怎么办?Curator应运而生!

  为了熟悉Apache Curator,接下来,将用curator来实现master选举的demo。

zookeeper典型应用场景之一:master选举的更多相关文章

  1. ZooKeeper典型应用场景

    ZooKeeper典型应用场景一览 数据发布与订阅(配置中心) 发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据发布到ZK节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新.例 ...

  2. ZooKeeper典型应用场景一览

    原文地址:http://jm-blog.aliapp.com/?p=1232 ZooKeeper典型应用场景一览 数据发布与订阅(配置中心) 发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据 ...

  3. ZooKeeper典型应用场景(转)

    ZooKeeper是一个高可用的分布式数据管理与系统协调框架.基于对Paxos算法的实现,使该框架保证了分布式环境中数据的强一致性,也正是基于这样的特性,使得ZooKeeper解决很多分布式问题.网上 ...

  4. ZooKeeper典型应用场景概览

    ZooKeeper是一个高可用的分布式数据管理与系统协调框架.基于对Paxos算法的实现,使该框架保证了分布式环境中数据的强一致性,也正是基于这样的特性,使得ZooKeeper解决很多分布式问题.网上 ...

  5. 搞懂分布式技术6:Zookeeper典型应用场景及实践

    搞懂分布式技术6:Zookeeper典型应用场景及实践 一.ZooKeeper典型应用场景实践 ZooKeeper是一个高可用的分布式数据管理与系统协调框架.基于对Paxos算法的实现,使该框架保证了 ...

  6. ZOOKEEPER典型应用场景解析

    zookeeper实现了主动通知节点变化,原子创建节点,临时节点,按序创建节点等功能.通过以上功能的组合,zookeeper能够在分布式系统中组合出很多上层功能.下面就看几个常用到的场景,及使用方式和 ...

  7. ZooKeeper 典型应用场景-Master选举

    master选举 1.使用场景及结构 现在很多时候我们的服务需要7*24小时工作,假如一台机器挂了,我们希望能有其它机器顶替它继续工作.此类问题现在多采用master-salve模式,也就是常说的主从 ...

  8. ZooKeeper 典型应用场景

    Zookeeper基础知识 1.zookeeper是一个类似hdfs的树形文件结构,zookeeper可以用来保证数据在(zk)集群之间的数据的事务性一致. 2.zookeeper有watch事件,是 ...

  9. ZooKeeper典型使用场景一览

    场景类别 典型场景描述(ZK特性,使用方法) 应用中的具体使用 数据发布与订阅 发布与订阅即所谓的配置管理,顾名思义就是将数据发布到zk节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新 ...

随机推荐

  1. 手机端的各种默认样式比如 ios的按钮变灰色

    1.ios按钮变灰色,给按钮加样式: -webkit-appearance: none; 2.有圆角话 ; } 3.去除Chrome等浏览器文本框默认发光边框 input:focus, textare ...

  2. 在Eclipse ee中成功使用jQuery UI插件

    关键代码截图如下:

  3. wap资费页面

    移动网关 CMWAP 返回vnd.wap.wml 用CMWAP接入点发一个http的请求时,移动的10.0.0.172这个代理网关会回一个确认页面,中国特色.处理的办法是检测到Content-type ...

  4. Integer cache

    View.findViewById采用深度遍历,找到第一个匹配的控件 Integer Cache public static void testIntegerCache() { Class cache ...

  5. 初探接口测试框架--python系列7

    点击标题下「蓝色微信名」可快速关注 坚持的是分享,搬运的是知识,图的是大家的进步,没有收费的培训,没有虚度的吹水,喜欢就关注.转发(免费帮助更多伙伴)等来交流,想了解的知识请留言,给你带来更多价值,是 ...

  6. 【翻译习作】 Windows Workflow Foundation程序开发-第一章02

    1.2      Windows Workflow概览 微软的Windows Workflow Foundation(简称WF)是.NET框架3.0版的一部分..NET3.0其它主要部分是Window ...

  7. 模糊查询(LIKE)and (PATINDEX() . CHARINDEX())

    SQL中的模糊查询一般来说使用模糊查询,大家都会想到LIKE  select * from table where a like '%字符%' 如果一个SQL语句中用多个 like模糊查询,并且记录条 ...

  8. break 和 continue

    break 和 continue 相同点: 都 用在循环体内,如 switch.for.while.do while的程序块中,用于控制程序循环语句的执行 不同点: break可以离开当前switch ...

  9. shell学习笔记(1):利用IFS打印用户和默认shell

    参考资料为:linux shell脚本攻略 作者sarath Lakshman 人民邮电出版社 shell:读取文件的每一行内容并输出 的写法1 目的:读取passwd文件,获得用户名和其默认的she ...

  10. 用代码给TABLE 添加字段,设置属性并编译

    AOTTableFieldList AOTTableFieldList ,TableFieldNode; TableName tableName = "SML_InventTableExt& ...