Zookeeper:分布式程序的基石

1、目录

一、zookeeper是什么?html

二、安装、配置、启动、监控java

三、javaApi基础用法node

四、应用场景linux

五、CAP理论/paxos算法 算法

2、zookeeper简介

官方版:zookeeper是 一个分布式的,开放源码的分布式应用程序协调服务,它包含一个简单的原语集,分布式应用程序能够基于它实现同步服务,配置维护和命名服务等。数据库

归纳版:zookeeper是“一致、 有头、数据树”。apache

一致:数据一致性(核心)。例如,有一个1000台机器的集群,我想修改1000台机器上相同的配置文件,那么咱们是一台一台去改吗?显然这种解决办法并不可靠,因此把配置信息注册到zookeeper,集群机器就成为了观察者,当配置信息改变的时候,通知集群全部的机器改变。从而保证了集群的配置文件一致性。vim

有头:有头领。为何要有头领?例如,有1000太机器的集群,客户端发送请求修改配置文件,那么谁去处理?有人可能说,我想指定那台机器,我就指定它ip呗?这样的话,那怎么实现负载均衡,手动控制吗?因此,选择一个头领,当客户端请求来的时候,由leader(头领)去控制,把任务分配给follower(跟班)。设计模式

数据树:绑定数据的树状结构。简单理解,它也是一个数据库,只不过它是存储于内存的树状结构的数据库。api

3、安装-配置-启动-监控

一、物理架构

  

     搭建zookeeper集群,zookeeper规定是总共集群的机器是奇数,而集群正常运行的条件是:存活的机器大于集群总数的二分之一。

二、安装配置(注意:集群的全部机器都须要设置)

  • 下载zookeeper-3.4.10.tar.gz

  • 解压:tar xvf zookeeper-3.4.10.tar.gz

  • 配置  

    • 切换到/zookeeper/conf目录:/usr/local/zookeeper-3.4.10/conf (个人路径)
    • 拷贝:cp zoo_sample.cfg zoo.cfg
    • 修改zoo.cfg:vim zoo.cfg
      • dataDir=/tmp/zookeeper (数据存储位置,生产环境须要修改,这个是linux的临时目录,可能会被删除)
      • 在配置文件底部添加一下内容:我这个配置了域名,若是没有配置域名就用ip。
      • server.1=master:2888:3888
      • server.2=slave2:2888:3888
      • server.3=slave3:2888:3888
    • 修改数据文件
      • 切换/tmp目录:cd /tmp
      • 建立zookeeper目录:mkdir zookeeper
      • 切换至zookeeper目录:cd /tmp/zookeeper
      • 建立myid文件:vim myid
      • master上,输入1保存;slave2,输入2保存;slave3,输入3保存。
  • 启动、观测

    • 切换至/zookeeper/bin目录:/usr/local/zookeeper-3.4.10/bin
    • 服务端
      • 启动:./zkServer.sh start
      • 查看:./zkServer.sh status
      • 中止:./zkServer.sh stop
    • jps(查看状态)
      • 2289 QuorumPeerMain
      • 2302 Jps
    • 客户端  .
      • ./zkCli.sh -server master:2181
      • create /tank tankservers
      • create /tank/server1 server1info
      • create /tank/server2 server2info
      • create /tank/server3 server3info
      • ls /tank
      • get /tank
      • set /tank tankserversinfo
      • get /tank
      • delete /tank/server3

4、javaApi基础用法

/** * .测试zookeeper的基本操做 * Creator:邱勇Aaron * DateTime:2017/6/25 14:51 */
public class ZookeeperBasicOperator { public static String connectString="192.168.0.100,192.168.0.102,192.168.0.103:2181"; private ZooKeeper zk; public ZookeeperBasicOperator(){ this(connectString,2000,null); } public ZookeeperBasicOperator(String connectString,int sessionTimeout,Watcher watcher){ try { zk=new ZooKeeper(connectString,sessionTimeout,watcher); }catch (IOException e){ e.printStackTrace(); } } public static void main(String [] args) throws Exception { connectString="192.168.0.100,192.168.0.102,192.168.0.103:2181"; ZookeeperBasicOperator zkOperator=new ZookeeperBasicOperator(connectString,2000,new MyWatcher()); //建立目录节点
        zkOperator.create("/testRootPath","testRootData",CreateMode.PERSISTENT); //查看目录节点
        zkOperator.getData("/testRootPath"); //建立目录子节点
        zkOperator.create("/testRootPath/testRootChild","testRootChild",CreateMode.PERSISTENT); //查看目录节点的子节点目录;
        zkOperator.getChildern("/testRootPath"); //从新设置目录节点的数据
        zkOperator.setData("/testRootPath","helloTestRootData"); //查看目录节点的数据
        zkOperator.getData("/testRootPath"); //删除目录节点的子数据节点
        zkOperator.delete("/testRootPath/testRootChild",-1); //删除目录节点
        zkOperator.delete("/testRootPath",-1); //关闭zookeeper
 zkOperator.closeZookeeper(); } //建立目录节点、孩子节点
    public void create(String path,String bindingData,CreateMode mode) throws KeeperException, InterruptedException { zk.create(path,bindingData.getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE,mode); System.out.println(); } //修改目录节点的数据
    public void setData(String path,String bindingData,int version) throws KeeperException, InterruptedException { zk.setData(path,bindingData.getBytes(),version); } public void setData(String path,String bindingData) throws KeeperException, InterruptedException { this.setData(path,bindingData,-1); } //删除节点
    public void delete(String path,int version) throws KeeperException, InterruptedException { zk.delete(path,version); } //得到目录节点数据
    public void getData(String path) throws KeeperException, InterruptedException { System.out.println(new String(zk.getData(path,false,null))); } //得到孩子节点数据
    public void getChildern(String path) throws KeeperException, InterruptedException { System.out.println(zk.getChildren(path,true)); } public void closeZookeeper(){ try { zk.close(); } catch (InterruptedException e) { e.printStackTrace(); } } } class MyWatcher implements Watcher{ @Override public void process(WatchedEvent watchedEvent) { System.out.println("已经触发了:"+watchedEvent.getType()+"事件!"); } }

5、ZooKeeper 典型的应用场景(转自:http://www.cnblogs.com/ggjucheng/p/3370359.html)

Zookeeper 从设计模式角度来看,是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理你们都关心的数据,而后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper 就将负责通知已经在 Zookeeper 上注册的那些观察者作出相应的反应,从而实现集群中相似 Master/Slave 管理模式,关于 Zookeeper 的详细架构等内部细节能够阅读 Zookeeper 的源码

下面详细介绍这些典型的应用场景,也就是 Zookeeper 到底能帮咱们解决那些问题?下面将给出答案。

统一命名服务(Name Service)

分布式应用中,一般须要有一套完整的命名规则,既可以产生惟一的名称又便于人识别和记住,一般状况下用树形的名称结构是一个理想的选择,树形的名称结构是一个有层次的目录结构,既对人友好又不会重复。说到这里你可能想到了 JNDI,没错 Zookeeper 的 Name Service 与 JNDI 可以完成的功能是差很少的,它们都是将有层次的目录结构关联到必定资源上,可是 Zookeeper 的 Name Service 更加是普遍意义上的关联,也许你并不须要将名称关联到特定资源上,你可能只须要一个不会重复名称,就像数据库中产生一个惟一的数字主键同样。

Name Service 已是 Zookeeper 内置的功能,你只要调用 Zookeeper 的 API 就能实现。如调用 create 接口就能够很容易建立一个目录节点。
 

配置管理(Configuration Management)

配置的管理在分布式应用环境中很常见,例如同一个应用系统须要多台 PC Server 运行,可是它们运行的应用系统的某些配置项是相同的,若是要修改这些相同的配置项,那么就必须同时修改每台运行这个应用系统的 PC Server,这样很是麻烦并且容易出错。

像这样的配置信息彻底能够交给 Zookeeper 来管理,将配置信息保存在 Zookeeper 的某个目录节点中,而后将全部须要修改的应用机器监控配置信息的状态,一旦配置信息发生变化,每台应用机器就会收到 Zookeeper 的通知,而后从 Zookeeper 获取新的配置信息应用到系统中。

图 2. 配置管理结构图

 

集群管理(Group Membership)

Zookeeper 可以很容易的实现集群管理的功能,若有多台 Server 组成一个服务集群,那么必需要一个“总管”知道当前集群中每台机器的服务状态,一旦有机器不能提供服务,集群中其它集群必须知道,从而作出调整从新分配服务策略。一样当增长集群的服务能力时,就会增长一台或多台 Server,一样也必须让“总管”知道。

Zookeeper 不只可以帮你维护当前的集群中机器的服务状态,并且可以帮你选出一个“总管”,让这个总管来管理集群,这就是 Zookeeper 的另外一个功能 Leader Election。

它们的实现方式都是在 Zookeeper 上建立一个 EPHEMERAL 类型的目录节点,而后每一个 Server 在它们建立目录节点的父目录节点上调用 getChildren(String path, boolean watch) 方法并设置 watch 为 true,因为是 EPHEMERAL 目录节点,当建立它的 Server 死去,这个目录节点也随之被删除,因此 Children 将会变化,这时 getChildren上的 Watch 将会被调用,因此其它 Server 就知道已经有某台 Server 死去了。新增 Server 也是一样的原理。

Zookeeper 如何实现 Leader Election,也就是选出一个 Master Server。和前面的同样每台 Server 建立一个 EPHEMERAL 目录节点,不一样的是它仍是一个 SEQUENTIAL 目录节点,因此它是个 EPHEMERAL_SEQUENTIAL 目录节点。之因此它是 EPHEMERAL_SEQUENTIAL 目录节点,是由于咱们能够给每台 Server 编号,咱们能够选择当前是最小编号的 Server 为 Master,假如这个最小编号的 Server 死去,因为是 EPHEMERAL 节点,死去的 Server 对应的节点也被删除,因此当前的节点列表中又出现一个最小编号的节点,咱们就选择这个节点为当前 Master。这样就实现了动态选择 Master,避免了传统意义上单 Master 容易出现单点故障的问题。

图 3. 集群管理结构图

void findLeader() throws InterruptedException { byte[] leader = null; try { leader = zk.getData(root + "/leader", true, null); } catch (Exception e) { logger.error(e); } if (leader != null) { following(); } else { String newLeader = null; try { byte[] localhost = InetAddress.getLocalHost().getAddress(); newLeader = zk.create(root + "/leader", localhost, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); } catch (Exception e) { logger.error(e); } if (newLeader != null) { leading(); } else { mutex.wait(); } } } 

共享锁(Locks)

共享锁在同一个进程中很容易实现,可是在跨进程或者在不一样 Server 之间就很差实现了。Zookeeper 却很容易实现这个功能,实现方式也是须要得到锁的 Server 建立一个 EPHEMERAL_SEQUENTIAL 目录节点,而后调用 getChildren方法获取当前的目录节点列表中最小的目录节点是否是就是本身建立的目录节点,若是正是本身建立的,那么它就得到了这个锁,若是不是那么它就调用exists(String path, boolean watch) 方法并监控 Zookeeper 上目录节点列表的变化,一直到本身建立的节点是列表中最小编号的目录节点,从而得到锁,释放锁很简单,只要删除前面它本身所建立的目录节点就好了。

图 4. Zookeeper 实现 Locks 的流程图
 

同步锁思路:

加锁: ZooKeeper 将按照以下方式实现加锁的操做: 1 ) ZooKeeper 调用 create ()方法来建立一个路径格式为“ _locknode_/lock- ”的节点,此节点类型为sequence (连续)和 ephemeral (临时)。也就是说,建立的节点为临时节点,而且全部的节点连续编号,即“ lock-i ”的格式。 2 )在建立的锁节点上调用 getChildren ()方法,来获取锁目录下的最小编号节点,而且不设置 watch 。 3 )步骤 2 中获取的节点刚好是步骤 1 中客户端建立的节点,那么此客户端得到此种类型的锁,而后退出操做。 4 )客户端在锁目录上调用 exists ()方法,而且设置 watch 来监视锁目录下比本身小一个的连续临时节点的状态。 5 )若是监视节点状态发生变化,则跳转到第 2 步,继续进行后续的操做,直到退出锁竞争。 解锁: ZooKeeper 解锁操做很是简单,客户端只须要将加锁操做步骤 1 中建立的临时节点删除便可。复制代码
同步锁代码
void getLock() throws KeeperException, InterruptedException{ List<String> list = zk.getChildren(root, false); String[] nodes = list.toArray(new String[list.size()]); Arrays.sort(nodes); if(myZnode.equals(root+"/"+nodes[0])){ doAction(); } else{ waitForLock(nodes[0]); } } void waitForLock(String lower) throws InterruptedException, KeeperException { Stat stat = zk.exists(root + "/" + lower,true); if(stat != null){ mutex.wait(); } else{ getLock(); } } 
复制代码

队列管理

Zookeeper 能够处理两种类型的队列:

  1. 当一个队列的成员都聚齐时,这个队列才可用,不然一直等待全部成员到达,这种是同步队列。
  2. 队列按照 FIFO 方式进行入队和出队操做,例如实现生产者和消费者模型。

同步队列用 Zookeeper 实现的实现思路以下:

建立一个父目录 /synchronizing,每一个成员都监控标志(Set Watch)位目录 /synchronizing/start 是否存在,而后每一个成员都加入这个队列,加入队列的方式就是建立 /synchronizing/member_i 的临时目录节点,而后每一个成员获取 / synchronizing 目录的全部目录节点,也就是 member_i。判断 i 的值是否已是成员的个数,若是小于成员个数等待 /synchronizing/start 的出现,若是已经相等就建立 /synchronizing/start。

用下面的流程图更容易理解:

图 5. 同步队列流程图
 

同步队列

 void addQueue() throws KeeperException, InterruptedException{ zk.exists(root + "/start",true); zk.create(root + "/" + name, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); synchronized (mutex) { List<String> list = zk.getChildren(root, false); if (list.size() < size) { mutex.wait(); } else { zk.create(root + "/start", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } } }

当队列没尽是进入wait(),而后会一直等待Watch的通知,Watch的代码以下:

public void process(WatchedEvent event) { if(event.getPath().equals(root + "/start") && event.getType() == Event.EventType.NodeCreated){ System.out.println("获得通知"); super.process(event); doAction(); } } 

FIFO 队列用 Zookeeper 实现思路以下:

实现的思路也很是简单,就是在特定的目录下建立 SEQUENTIAL 类型的子目录 /queue_i,这样就能保证全部成员加入队列时都是有编号的,出队列时经过 getChildren( ) 方法能够返回当前全部的队列中的元素,而后消费其中最小的一个,这样就能保证 FIFO。

生产者代码

 boolean produce(int i) throws KeeperException, InterruptedException{ ByteBuffer b = ByteBuffer.allocate(4); byte[] value; b.putInt(i); value = b.array(); zk.create(root + "/element", value, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); return true; }
复制代码
消费者代码
int consume() throws KeeperException, InterruptedException{ int retvalue = -1; Stat stat = null; while (true) { synchronized (mutex) { List<String> list = zk.getChildren(root, true); if (list.size() == 0) { mutex.wait(); } else { Integer min = new Integer(list.get(0).substring(7)); for(String s : list){ Integer tempValue = new Integer(s.substring(7)); if(tempValue < min) min = tempValue; } byte[] b = zk.getData(root + "/element" + min,false, stat); zk.delete(root + "/element" + min, 0); ByteBuffer buffer = ByteBuffer.wrap(b); retvalue = buffer.getInt(); return retvalue; } } } } 
6、CAP理论与Paxos算法

CAP理论:

一致性(Consistency):在分布式系统中的全部数据备份,在同一时刻一样的值。(等同于全部节点访问同一份最新的数据副本)
可用性(Availability):在集群中一个节点出现故障,集群总体是否还能响应客户端的读写请求。(对数据更新具有高可用)
分区容错性(Partition Tolerance): 以实际效果而言,分区至关于对通讯的时限要求。系统若是不能在时限内达成数据一致性,就意味着发生了分区的状况,必须就当前操做在C和A之间作出选择。
总体总结来讲,zookeeper集群,只能知足三个条件的其中两个,例如保证一致性与可用性,就不能保证分区容错性,其余同理。
 

Paxos算法(选举算法):详情参考http://www.cnblogs.com/yuyijq/p/4116365.html

新集群启动时候的选举过程,启动的时候就是根据zoo.cfg里的配置,向各个节点广播投票,通常都是选投本身。而后收到投票后就会进行进行判断。若是某个节点收到的投票数超过一半,那么它就是leader了。 

了解了这个过程,咱们来看看另一个问题:

一个集群有3台机器,挂了一台后的影响是什么?挂了两台呢? 

挂了一台:挂了一台后就是收不到其中一台的投票,可是有两台能够参与投票,按照上面的逻辑,它们开始都投给本身,后来按照选举的原则,两我的都投票给其中一个,那么就有一个节点得到的票等于2,2 > (3/2)=1 的,超过了半数,这个时候是能选出leader的。

挂了两台: 挂了两台后,怎么弄也只能得到一张票, 1 不大于 (3/2)=1的,这样就没法选出一个leader了。

7、版权声明

  做者:邱勇Aaron

  出处:http://www.cnblogs.com/qiuyong/

  您的支持是对博主深刻思考总结的最大鼓励。

  本文版权归做者全部,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,尊重做者的劳动成果。

  参考:马士兵zookeeper、zookeeper官方文档

     选举算法:http://www.cnblogs.com/yuyijq/p/4116365.html

     应用场景:http://www.cnblogs.com/ggjucheng/p/3370359.html

相关文章
相关标签/搜索