对于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. [kuangbin带你飞]专题九 连通图

        ID Origin Title   76 / 163 Problem A POJ 1236 Network of Schools   59 / 177 Problem B UVA 315 Ne ...

  2. select into from 和 insert into select 的用法和区别

    select into from 和 insert into select都是用来复制表,两者的主要区别为: select into from 要求目标表不存在,因为在插入时会自动创建.insert ...

  3. What is Split Brain in Oracle Clusterware and Real Application Cluster (文档 ID 1425586.1)

    In this Document   Purpose   Scope   Details   1. Clusterware layer   2. Real Application Cluster (d ...

  4. Hibernate高级查询QBC条件设置——Restrictions用法 引自:http://www.cnblogs.com/evon168/archive/2010/10/29/1863059.html

    方法说明 方法 说明 Restrictions.eq = Restrictions.allEq 利用Map来进行多个等于的限制 Restrictions.gt > Restrictions.ge ...

  5. The 2013 ACM-ICPC Asia Changsha Regional Contest - J

    Josephina and RPG Time Limit: 2 Seconds      Memory Limit: 65536 KB      Special Judge A role-playin ...

  6. 用sql语句生成sqlserver数据库表的数据字典

    THEN O.name ELSE N'' END, 表描述 THEN PTB.[value] END,N''), 字段序号=C.column_id, 字段名称=C.name, 字段描述=ISNULL( ...

  7. Android API 中文 ListView

    正文 一.结构 public class RatingBar extends AbsSeekBar java.lang.Object android.view.View android.view.Vi ...

  8. C#调用C dll,结构体传参

    去年用wpf弄了个航线规划软件,用于生成无人机喷洒农药的作业航线,里面包含了不少算法.年后这几天将其中的算法移植到C,以便其他同事调用.昨天在用C#调用生成的dll时,遇到一些问题,折腾了好久才解决. ...

  9. Android开发-API指南- Calendar Provider

    Calendar Provider 英文原文:http://developer.android.com/guide/topics/providers/calendar-provider.html 采集 ...

  10. ICDM 2007

    Language-Independent Set Expansion of Named Entities Using the Web. Chao Wang, Venu Satuluri, Sriniv ...