代码改变世界

ZooKeeper -- 分布式开源协调服务

2014-11-23 17:47  hduhans  阅读(1073)  评论(0编辑  收藏  举报

  ZooKeeper是一个为分布式应用所设计的开源协调服务,适用于大型的分布式系统,可以提供统一命名服务状态同步服务集群管理分布式应用配置项的管理等服务。ZooKeeper支持Java和C两种编程语言的接口,可以很方便地实现一致性、组管理、leader选举和某些协议。

一、ZooKeeper简介

1、一致性

   1) 顺序一致性:客户端的更新顺序与他们被发送的顺序相一致;

   2) 原子性:更新操作要么全部成功,要么全部失败;

   3) 单系统镜像:无论客户端连接到哪一个服务器,都可以看到相同的ZooKeeper视图;

   4) 可靠性:一旦一个更新操作被应用,那么在客户端再次更新它之前,其值将不会被改变;

   5) 实时性:在特定的一段时间内,系统的任何变更都将被客户端检测到;

2、为什么使用ZooKeeper

   大部分分布式应用需要一个主控、协调器或控制器来管理物理分布的子进程(如资源、任务分配等),而目前大部分应用需要开发私有的协调程序,缺乏一个通用的机制。此外协调程序的反复编写浪费,且难以形成通用、伸缩性好的协调器,由此诞生了一个能提供通用的分布式锁服务,用以协调分布式应用系统的协调服务ZooKeeper。

3、Zookeeper节点类型

   Zookeeper的节点Znode有两种类型,分别是emphemeral(短暂的)和persistent(持久的),并且节点类型在创建时制定后就不可更改。

   emphmeral节点在客户端会话结束时就自动删除短暂Znode下不可以有子节点

   persistent节点不依赖于客户端会话,只有当客户端明确执行删除操作时,该节点才会被删除

4、ZooKeeper的数据模型

   在ZooKeeper中存在着节点的概念,通常称为Znode。Znode既含有数据。又可以用来标识路径,因此既可以被看作是一个文件,又可以被看作是一个目录。Znode的目录结构如下图所示:

图1.1 ZooKeeper文件结构  

   说明:

   1) 每个Znode创建时会被自动编号,并且其有一个唯一的路径标识,如/SERVER2节点的标识就为/APP3/SERVER2;

   2) Znode与操作系统的文件不同,Znode可以拥有子Znode,并且可以存数据;

   3) Znode中的数据可以有多个版本,比如某一个路径下存有多个数据版本,那么查询这个路径下的数据就需要带上版本;

   4) Znode可以是临时节点,一旦创建这个znode的客户端与服务器失去联系,这个znode也将自动删除,Zookeeper的客户端和服务器通信采用长连接方式,每个客户端和服务器通过心跳来保持连接,这个连接状态称为session,如果znode是临时节点,这个session失效,znode也就删除了;

   5) Znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的集中管理,集群管理,分布式锁等等;

5、ZooKeeper的角色

   1) 领导者(leader),负责进行投票的发起和决议,更新系统状态;

   2) 学习者(learner),包括跟随者(follower)和观察者(observer),follower用于接受客户端请求并向客户端返回结果,在选主过程中参与投票;

   3) 客户端(client),请求发起方;

   观察者observer可以接受客户端连接,将写请求转发给leader,但observer不参加投票过程,只同步leader的状态,observer的目的是为了扩展系统,提高读取速度。

6、Zookeeper配置

   Zookeeper配置文件在conf文件夹中,几个重要配置参数说明:

   1) tickTime:是Zookeeper服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个tickTime时间就会发送一个心跳,默认值2000(毫秒);

   2) initLimit:这个配置项是用来配置Zookeeper接受Foller客户端初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过 10 个心跳的时间(也就是tickTime)长度后Zookeeper服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。默认值为10,也总的时间长度就是10*2000=20秒;

   3) syncLimit:这个配置项标识Leader与Follower之间发送消息,请求和应答时间长度,最长不能超过多少个tickTime的时间长度。默认值为5,也就是5*2000=10秒;

   4) dataDir:是Zookeeper保存数据的目录地址,默认情况下,Zookeeper的写数据的日志文件也保存在这个目录中;

   5) clientPort:指客户端连接Zookeeper服务器的端口,Zookeeper会监听这个端口,接受客户端的访问请求;

   6) server.A=B:C:D:A是一个数字,表示第几号服务器;B为IP或主机名;C表示的是这个服务器与集群中的Leader服务器交换信息的端口;D表示的是万一集群中的Leader服务器挂了,需要一个端口来重新进行选举,选出一个新的Leader,而这个端口就是用来执行选举时服务器相互通信的端口;

   注意,这里的“客户端”指的不是用户连接Zookeeper服务器的客户端,而是Zookeeper服务器集群中连接到Leader的Follower服务器

二、ZooKeeper集群模式安装

   集群模式下安装ZooKeeper,至少需要三个节点,并且最好使用奇数台的机器。如果想要集群能够忍受m台机器的故障,那么整个集群至少需要2m+1台机器。

   本实例安装版本:zookeeper-3.4.5.tar.gz,集群共有三个节点:hadoop0、hadoop1和hadoop2。详细安装步骤如下:

1、解压缩zookeerper-3.4.5.tar.gz,并将文件移动至/usr/local下重命名为zk,执行命令:①tar -zxvf  zookeerper-3.4.5.tar.gzmv zookeeper-3.4.5 /usr/local/zk

2、配置环境变量,执行命令:vi /etc/profile,增加一行:export ZOOKEEPER_HOME=/usr/local/zk,并在环境变量export PATH=.:.....:$PATH中增加:$ZOOKEEPER_HOME/bin,然后执行命令:source /etc/profile使配置文件立即生效

3、进入conf目录,执行命令:mv zoo_sample.cfg zoo.cfg,然后编辑此配置文件,执行命令:vi zoo.cfg

   修改:dataDir=/usr/local/zk/data

   新增:server.0=hadoop0:2888:3888

      server.1=hadoop1:2888:3888

      server.2=hadoop2:2888:3888

4、创建数据文件夹,执行命令:mkdir /usr/local/zk/data,并在data目录下创建文件myid,值为0

5、通过scp命令将zk安装目录复制到hadoop1和hadoop2中,同时将hadoop0的配置文件/etc/profile复制到其他节点中,执行命令(只例举hadoop1):

   scp -r /usr/local/zk/ hadoop1:/usr/local/  --将zk发送到hadoop1

   scp /etc/profile hadoop1:/etc/   --将环境变量配置文件发送到hadoop1

   source /etc/profile   --在hadoop1中执行,使得配置文件立即生效

6、把hadoop1和hadoop2中相应的myid文件内容分别改为1和2,分别与配置文件zoo.cfg中的server.x对应

7、启动,在三个节点上分别执行命令:zkServer.sh start。启动后,检验当前节点的身份可执行命令:zkServer.sh status。进入客户端命令:zkCli.sh

三、使用Java操作Zookeeper

   在Zookeeper客户端Cli.sh中可以执行的操作,在Java中都可以通过调用Zookeeper的API来完成。执行程序需要通过Zookeeper的2181端口来进行连接,因此建议测试环境下将防火墙关闭。

   操作示例代码如下(需导入zookeeper-x.x.x.jar及lib下的相关jar包,或导入Zookeeper的maven资源):

package com.hicoor.zookeeper;

import java.util.List;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;

public class ZookeeperDemo {

    private static String connectString = "172.19.7.31:2181";
    private static int sessionTimeout = 999999;
    private static String testNode = "/hans";
    
    /**
     * 为确保程序顺利运行,运行前最好关闭服务端的防火墙
     */
    public static void main(String[] args) throws Exception {
        //监视器
        Watcher watcher = new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("触发事件:" + event);
            }
        };
        //创建连接实例
        ZooKeeper zooKeeper = new ZooKeeper(connectString, sessionTimeout, watcher);
        System.out.println("获取连接:" + zooKeeper);
        
        //创建节点并设置值
        zooKeeper.create(testNode, "haha.mx".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        
        //重新设置节点值(不存在报异常)
        zooKeeper.setData(testNode, "hicoor.com".getBytes(), -1);
        
        //读取节点值
        getNodeData(watcher, zooKeeper);
        
        //删除节点
        zooKeeper.delete(testNode, -1);
        System.out.println("删除节点");
        
        List<String> children = zooKeeper.getChildren("/", watcher);
        System.out.println(children);
        
        //关闭连接实例
        zooKeeper.close();
    }

    //读取节点值
    private static void getNodeData(Watcher watcher, ZooKeeper zooKeeper)
            throws KeeperException, InterruptedException {
        
        byte[] data = zooKeeper.getData(testNode, watcher, null);
        System.out.println("读取的值:"+new String(data));
    }

}
View Code