zookeeper文档

本文介绍的zookeeper是以3.4.6稳定版为基础,最新版本可以通过官网http://zookeeper.apache.org/获取,下面从单机模式和集群模式两个方面介绍zookeeper的安装和配置。

单机模式

conf文件夹下,有zoo_sample.cfglog4j.properties,你需要做的就是将zoo_smaple.cfg重命名为zoo.cfg,因为Zookeeper在启动时会找到这个文件作为默认配置文件。

下面介绍配置文件中的各个配置项的意义:

tickTime:这个时间是作为zookeeper服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每隔tickTime时间就会发送一个心跳。

dataDirzookeeper保存数据的目录。

clientPort:客户端连接zookeeper服务器的端口,zookeeper会监听这个端口,接受客户端的访问请求。

集群模式

zookeeper不仅可以单机提供服务,同时也支持多机组成集群来提供服务。实际上zookeeper还支持另外一种伪集群的方式,也就是可以在一台物理机上运行多个zookeeper实例。集群的安装配置也很简单,所要做的就是增加几个配置项:

initLimit=5

syncLimit=2

server.1=192.168.211.1:2888:3888

server.2=192.168.211.2:2888:3888

 

initLimit:这个配置项是用来配置zookeeper接受客户端(这里说的客户端不是用户连接zookeeper服务器的客户端,而是zookeeper服务集群中连接到LeaderFollower服务器)初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过n个心跳的时间长度后zookeeper服务器还没有收到客户端的返回信息,那么就表明这个客户端连接失败。例如设置为5,那么总的时间长度就是5*tickTime

syncLimit:这个配置项标识LeaderFollower之间发送消息,请求和应答时间长度,最长不能超过多少个tickTime的时间长度,例如设置为2,那么总的时间长度就是2*tickTime

server.A=B:C:D :其中A是一个数字,表示这个是第几号服务器;B是这个服务器的ip地址;C表示这个服务器与集群中的Leader服务器交换信息的端口;D表示的是万一集群中的Leader服务器挂了,需要一个端口来重新进行选举,选出一个新的Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于B一样,所以不同的zookeeper实例通信端口号不能一样,所以要给它们分配不同的端口号。

 

除了修改zoo.cfg配置文件,集群模式下还要配置一个文件myid,这个文件在dataDir目录下,这个文件里面就有一个数据就是A的值,zookeeper启动时会读取这个文件,拿到里面的数据与zoo.cfg里面的配置信息比较从而判断到底是哪个server

windows下,启动zkServer.cmd打开zookeeper服务器,zkCli.cmd打开客户端;

linux下, zkServer.sh start/stop打开/关闭zookeeper服务器,zkCli.sh -server zookeeper:2181打开客户端。

在客户端可以使用help命令查看相关命令。

 

数据模型

zookeeper会维护一个具有层次关系的数据结构,它非常类似于一个标准的文件系统,如下图所示:

 

zookeeper这种数据结构有如下特点:

1. 每个子目录项如NameService都被称作为znode,这个znode是被它所在的路径唯一标识,如Server1这个znode的标识为/NameService/Server1

2. znode可以有子节点目录,并且每个znode可以存储数据,注意EPHEMERAL类型的目录节点不能有子节点目录

3. znode是有版本的,每个znode中存储的数据可以有多个版本,也就是一个访问路径中可以存储多份数据

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

5. znode的目录名可以自动编号,如App1已经存在,再创建的话,将会自动命名为App2

6. znode可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个是zookeeper的核心特性,zookeeper的很多功能都是基于这个特性实现的。

基本操作

下面给出基本的操作 ZooKeeper 的示例代码以便能对 ZooKeeper 有直观的认识。下面的代码包括了创建与 ZooKeeper 服务器的连接以及最基本的数据操作:

// 创建一个与服务器的连接

 ZooKeeper zk = new ZooKeeper("localhost:" + CLIENT_PORT,

        ClientBase.CONNECTION_TIMEOUT, new Watcher() {

            // 监控所有被触发的事件

            public void process(WatchedEvent event) {

                System.out.println("已经触发了" + event.getType() + "事件!");

            }

        });

 // 创建一个目录节点

 zk.create("/testRootPath", "testRootData".getBytes(), Ids.OPEN_ACL_UNSAFE,

   CreateMode.PERSISTENT);

 // 创建一个子目录节点

 zk.create("/testRootPath/testChildPathOne", "testChildDataOne".getBytes(),

   Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);

 System.out.println(new String(zk.getData("/testRootPath",false,null)));

 // 取出子目录节点列表

 System.out.println(zk.getChildren("/testRootPath",true));

 // 修改子目录节点数据

 zk.setData("/testRootPath/testChildPathOne","modifyChildDataOne".getBytes(),-1);

 System.out.println("目录节点状态:["+zk.exists("/testRootPath",true)+"]");

 // 创建另外一个子目录节点

 zk.create("/testRootPath/testChildPathTwo", "testChildDataTwo".getBytes(),

   Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);

 System.out.println(new String(zk.getData("/testRootPath/testChildPathTwo",true,null)));

 // 删除子目录节点

 zk.delete("/testRootPath/testChildPathTwo",-1);

 zk.delete("/testRootPath/testChildPathOne",-1);

 // 删除父目录节点

 zk.delete("/testRootPath",-1);

 // 关闭连接

 zk.close();

输出的结果如下:

已经触发了 None 事件!

 testRootData

 [testChildPathOne]

目录节点状态:[5,5,1281804532336,1281804532336,0,1,0,0,12,1,6]

已经触发了 NodeChildrenChanged 事件!

 testChildDataTwo

已经触发了 NodeDeleted 事件!

已经触发了 NodeDeleted 事件!

 

当对目录节点监控状态打开时,一旦目录节点的状态发生变化,Watcher 对象的 process 方法就会被调用。

应用场景

配置管理

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

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

 

集群管理

zookeeper能够很容易的实现集群管理的功能,如有多台Server组成一个服务集群,那么必须要一个“总管”知道当前集群中每台机器的服务状态,一旦有机器不能提供服务,集群中其它机器必须知道,从而做出调整重新肥培服务测略。同样当增加集群的服务能力时,就会增加一台或多台Server,同样也必须让“总管”知道。

zookeeper不仅能够够帮助你维护当前的集群中机器的服务状态,而且能够帮你选出一个“总管”,让这个总管来管理集群,这就是zookeeper的另一个功能Leader Election

他们的实现方式都是在zookeeper上创建一个EPHEMERAL类型的目录节点,然后每个Server在它们创建目录节点的父目录节点上调用getChildren(String path, boolean watch)方法并设置watchtrue,由于是EPHEMERAL目录节点,当创建它的Server死去,这个目录节点也随之被删除,所以Children将会变化,这时getChildren上的watch将会被调用,所以其它Server就知道已经有某台Server死去了。新增Server也是同样的原理。

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

 

集群管理结构图

 

 

Leader Election 关键代码

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 上目录节点列表的变化,一直到自己创建的节点是列表中最小编号的目录节点,从而获得锁,释放锁很简单,只要删除前面它自己所创建的目录节点就行了。

Zookeeper 实现 Locks 的流程图

 

同步锁的关键代码

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

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

同步队列流程图

 

同步队列的关键代码如下

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;

                }

            }

        }

 }

配置参数

最小配置

tickTime:这个时间是作为zookeeper服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每隔tickTime时间就会发送一个心跳。

dataDirzookeeper保存数据的目录。

clientPort:客户端连接zookeeper服务器的端口,zookeeper会监听这个端口,接受客户端的访问请求。

高级配置(可选,有的需要直接通过系统属性进行设置)

dataLogDir将事务日志存储在该路径下比较重要这个日志存储的设备效率会影响ZK的写吞吐量。

globalOutstandingLimit (Java system property: zookeeper.globalOutstandingLimit)默认值是1000,限定了所有连接到服务器上但是还没有返回响应的请求个数(所有客户端请求的总数,不是连接总数),这个参数是针对单台服务器而言,设定太大可能会导致内存溢出。

preAllocSize (Java system property: zookeeper.preAllocSize)默认值64M,以KB为单位,预先分配额定空间用于后续transactionlog 写入,每当剩余空间小于4K时,就会又分配64M,如此循环。如果SNAP做得比较频繁(snapCount比较小的时候),那么请减少这个值。

snapCount (Java system property: zookeeper.snapCount)默认值100,000,当transaction每达到snapCount/2+rand.nextInt(snapCount/2)时,就做一次SNAPSHOT,默认情况下是50,000~100,000transactionlog就会做一次,之所以用随机数是为了避免所有服务器可能在同一时间做snapshot.

 traceFile (Java system property: requestTraceFile)如果这个选项被定义请求将会记录到一个名为traceFile.year.month.day的跟踪文件。使用这个选项提供了有用的调试信息,但会影响性能。(注意系统属性没有zookeeper前缀并配置变量名称是不同的系统属性。是的——这不是一致的烦人的。)

maxClientCnxns默认值是60,一个客户端能够连接到同一个服务器上的最大连接数,根据IP来区分。如果设置为0,表示没有任何限制。设置该值一方面是为了防止DoS攻击。

clientPortAddress(New in 3.3.0)clientPort匹配,表示某个IP地址,如果服务器有多个网络接口(多个IP地址),如果没有设置这个属性,则clientPort会绑定到所有IP地址上,否则只绑定到该设置的IP地址上。

minSessionTimeout(New in 3.3.0)最小的session time时间,默认值是2tick time客户端设置的session time 如果小于这个值,则会被强制协调为这个最小值。

 maxSessionTimeout(New in 3.3.0)最大的session time 时间,默认值是20tick time客户端设置的session time 如果大于这个值,则会被强制协调为这个最大值。

fsync.warningthresholdms(New in 3.3.4Java system property: fsync.warningthresholdms)事务日志输出时,如果调用fsync方法超过指定的超时时间,那么会在日志中输出警告信息。默认是1000msThis value can only be set as a system property.

autopurge.snapRetainCount (New in 3.4.0) autopurge.purgeInterval参数搭配使用指定了需要保留的文件数目。默认是保留3个。

autopurge.purgeInterval(New in 3.4.0)  ZK提供了自动清理事务日志和快照文件的功能,这个参数指定了清理频率,单位是小时,需要配置一个1或更大的整数,默认是0,表示不开启自动清理功能。

syncEnabled(New in 3.4.6,3.5.0Java system property:zookeeper.observer.syncEnabled)The observers now log transaction and write snapshot to disk by default like the participants. This reduces the recovery time of the observers on restart. Set to "false" to disable this feature. Default is "true".

集群配置项

electionAlg领导选举算法,默认是3(fast leader election,基于TCP)0表示leader选举算法(基于UDP)1表示非授权快速选举算法(基于UDP)2表示授权快速选举算法(基于UDP),目前12算法都没有应用,不建议使用,0算法未来也可能会被干掉,只保留3(fast leader election)算法,因此最好直接使用默认就好。

initLimit:这个配置项是用来配置zookeeper接受客户端(这里说的客户端不是用户连接zookeeper服务器的客户端,而是zookeeper服务集群中连接到LeaderFollower服务器)初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过n个心跳的时间长度后zookeeper服务器还没有收到客户端的返回信息,那么就表明这个客户端连接失败。例如设置为5,那么总的时间长度就是5*tickTime

syncLimit:这个配置项标识LeaderFollower之间发送消息,请求和应答时间长度,最长不能超过多少个tickTime的时间长度,例如设置为2,那么总的时间长度就是2*tickTime

leaderServes(Java system property: zookeeper.leaderServes):默认yes 如果该值不是no,则表示该服务器作为leader时是需要接受客户端连接的。为了获得更高吞吐量,当服务器数三台以上时一般建议设置为no

cnxTimeout(Java system property: zookeeper.cnxTimeout)默认值是5000单位ms 表示leaderelection时打开连接的超时时间,只用在算法3中。

server.A=B:C:D :其中A是一个数字,表示这个是第几号服务器;B是这个服务器的ip地址;C表示这个服务器与集群中的Leader服务器交换信息的端口;D表示的是万一集群中的Leader服务器挂了,需要一个端口来重新进行选举,选出一个新的Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于B一样,所以不同的zookeeper实例通信端口号不能一样,所以要给它们分配不同的端口号。

group.x=nnnnn[:nnnnn]Enables a hierarchical quorum construction."x" is a group identifier and the numbers following the "=" sign correspond to server identifiers. The left-hand side of the assignment is a colon-separated list of server identifiers. Note that groups must be disjoint and the union of all groups must be the ZooKeeper ensemble.

weight.x=nnnnnUsed along with "group", it assigns a weight to a server when forming quorums. Such a value corresponds to the weight of a server when voting. There are a few parts of ZooKeeper that require voting such as leader election and the atomic broadcast protocol. By default the weight of server is 1. If the configuration defines groups, but not weights, then a value of 1 will be assigned to all servers.

授权认证配置项

zookeeper.DigestAuthenticationProvider.superDigest

(Java system property only: zookeeper.DigestAuthenticationProvider.superDigest)设置这个值是为了确定一个超级用户,它的值格式为super:<base64encoded(SHA1(idpassword))> ,一旦当前连接addAuthInfo超级用户验证通过,后续所有操作都不会checkACL。操作具体步骤:DigestAuthenticationProvider.generateDigest("super:admin123");生成的值为A,将zkServer.sh中添加-Dzookeeper.DigestAuthenticationProvider.superDigest=生成的A,最后zk.addAuthInfo("digest", "super:admin123".getBytes());

Pingbacks已打开。

引用地址

评论
发表评论