当前访客身份:游客 [ 登录  | 注册加入尚学堂]
直播

helloworld

拥有积分:8201
莫问前路在何方!!!!

博客分类

笔记中心

课题中心

提问中心

答题中心

解答题中心

zookeeper原理及应用场景

发表于 2年前 (2014-12-31 11:13:31)  |  评论(1)  |  阅读次数(1837)| 0 人收藏此文章,   我要收藏   

zookeeper原理及应用场景

一.Zookeeper感性介绍

1.    主观理解:

zookeeper可以理解为一个集群共享一份数据,并且可以对每个接入到zookeeper的客户端提供数据(权限允许的情况下)和在数据改变的情况下通知所有连接的客户端,为了便于处理和操作数据,此处的客户端一般为java程序接入,来处理复杂逻辑,这里的一份数据是以节点的形式存储的而且存储时二进制流的形式,所以可以这样理解这份数据,它是相当于key-value形式的数据,其中节点名为key,节点数据为value,但是比一般的键值对数据存储更高级的地方有以下四点:

(1).类似文件存储结构的层级关系,父节点可以获取子节点

(2).byte流形式存储,也就意味着可以存储任何类型的数据,只是为了保证效率默认只能存储最大1M大小的数据,只是zookeeper只是致力于管理集群,节点就是用来存储少许关键配置数据,所以1M足以

(3).拥有权限控制,zookeeper类提供AC访问节点权限的控制

(4).能提供分布式应用所必需的高级功能,事物锁

2.    zookeeper特色

两阶段提交(Two-phased Commit

两阶段提交协议可以让分布式系统的所有客户端决定究竟提交某一事务或还是终止该事务。

Zookeeper 中,你可以让协调者(coordinator)创建事务节点,比如,"/app/Tx",从而实现一个两阶段提交协议。 当协调者(coordinator)创建了子节点时,子节点内容是未定义的,由于每个事务参与方都会从协调者接收事务,参与方读取每个子节点并设置监视。然后每个参与方通过向与自身相关的 Znode 节点写入数据来投票提交(commit中止(abort事务。一旦写入完成,其他的参与方会被通知到,当所有的参与方都投完票后,协调者就可以决定究竟是提交(commit中止(abort事务。注意,如果某些参与方投票中止,节点是可以决定提前中止事务的。

该实现方法有趣的地方在于协调者的唯一作用是决定参与方的组(the group of sites),创建 Zookeeper 节点, 将事务传播到相应的参与方,实际上,Zookeeper 可以通过将消息写入事务节点来传播事务。

上述讨论的方法存在两个明显的缺点,一是消息的复杂性,复杂度为 O(n²),另外一个是仅通过临时节点不能判断某些参与方是否失效,为了利用临时节点检测参与方是否失效,必须参与方创建该节点。

为了解决第一个问题,你可以将系统设置成只有一个协调者可以收到事务节点状态的变化,一旦协调者达成意见后通知其他参与方, 该方法可扩展性较强,但是速度很慢,因为所有的通信都指向协调者。

为了解决第二个问题,你可以让参与方把事务传播到参与方,并让每个参与方创建自己的临时节点。

Leader 选举(Leader Election

Zookeeper 实现 Leader 选举简单做法是在创建代表 “proposals” 客户端的 Znode 节点时设置 SEQUENCE|EPHEMERAL 标志。基本想法是创建一个节点,比如 "/election",然后在创建子节点时"/election/n_"设置标志 SEQUENCE|EPHEMERAL. 当设置顺序节点SEQUENCE标志时,Zookeeper 会在 "/election" 子节点的创建过程中自增子节点名称后缀的序号,最小后缀序号的 Znode 节点表示Leader

然而,还没完,监视 Leader 失效也是非常重要的,当前的 Leader 失效后需要一个新的客户端起来接替旧的 Leader 的位置。一个简单的方式是让所有的应用进程监视当前序号最小的 Znode 节点, 并在当前 序号最小的 Znode 节点失效是检查他们是否为新的 Leader(注意当前序号最小的节点可能会随着 Leader 的消失而消失,他们可能是该Leader 节点的临时子节点). 但是这会导致'羊群效应(herd effect)":在当前 Leader 失效后,其他所有的进程(节点)将会收到通知,并在 "/election" 节点上执行 getChildren()来获取"/election"节点的子节点列表,如果客户端数目很大,它会使得Zookeeper服务器处理的操作次数急剧上升。为了避免羊群效应,客户端只需要监视 Znode 节点中的下一个节点就足够。如果某个客户端收到了它正在监视的节点消失的通知,它将成为新的 Leader,因为此时没有其它的 Znode 节点的序号比它小。所以这就避免了羊群效应,并且客户端也没有必要监视同一个最小的 Znode 节点。

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. 集群管理结构图图 3. 集群管理结构图 

这部分的示例代码如下:

 

清单 3. 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();

            }

        }

    }

配置管理(Configuration Management

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

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

共享锁(Locks

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

 

4. Zookeeper 实现 Locks 的流程图
图 4. Zookeeper 实现 Locks 的流程图 

同步锁的实现代码如下:

 

清单 4. 同步锁的关键代码

                                    

 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. 同步队列流程图
图 5. 同步队列流程图 
同步队列的关键代码如下:

 

清单 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

下面是生产者和消费者这种队列形式的示例代码,完整的代码请看附件:

 

清单 6. 生产者代码

             

 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;

    }

 

 

清单 7. 消费者代码

             

 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;

                }

            }

        }

 }

常用的四字命令

参数名

说明

conf

输出server的详细配置信息。New in 3.3.0

$>echo conf|nc localhost 2181
clientPort=2181
dataDir=/home/test/taokeeper/zk_data/version-2
dataLogDir=/test/admin/taokeeper/zk_log/version-2
tickTime=2000
maxClientCnxns=1000
minSessionTimeout=4000
maxSessionTimeout=40000
serverId=2
initLimit=10
syncLimit=5
electionAlg=3
electionPort=3888
quorumPort=2888
peerType=0

cons

输出指定server上所有客户端连接的详细信息,包括客户端IP,会话ID等。
New in 3.3.0
类似于这样的信息:

$>echo cons|nc localhost 2181
/1.2.3.4:43527[1](queued=0,recved=152802,sent=152806,sid=0x2389e662b98c424,lop=PING,
est=1350385542196,to=6000,lcxid=0×114,lzxid=0xffffffffffffffff,lresp=1350690663308,
llat=0,minlat=0,avglat=0,maxlat=483)
……

crst

功能性命令。重置所有连接的统计信息。New in 3.3.0

dump

这个命令针对Leader执行,用于输出所有等待队列中的会话和临时节点的信息。

envi

用于输出server的环境变量。包括操作系统环境和Java环境。

ruok

用于测试server是否处于无错状态。如果正常,则返回“imok”,否则没有任何响应。
注意:ruok不是一个特别有用的命令,它不能反映一个server是否处于正常工作。“stat”命令更靠谱。

stat

输出server简要状态和连接的客户端信息。

srvr

stat类似,New in 3.3.0

$>echo stat|nc localhost 2181
Zookeeper version: 3.3.5-1301095, built on 03/15/2012 19:48 GMT
Clients:
/10.2.3.4:59179[1](queued=0,recved=44845,sent=44845)

Latency min/avg/max: 0/0/1036
Received: 2274602238
Sent: 2277795620
Outstanding: 0
Zxid: 0xa1b3503dd
Mode: leader
Node count: 37473

$>echo srvr|nc localhost 2181
Zookeeper version: 3.3.5-1301095, built on 03/15/2012 19:48 GMT
Latency min/avg/max: 0/0/980
Received: 2592698547
Sent: 2597713974
Outstanding: 0
Zxid: 0xa1b356b5b
Mode: follower
Node count: 37473

srst

重置server的统计信息。

wchs

列出所有watcher信息概要信息,数量等:New in 3.3.0

$>echo wchs|nc localhost 2181
3890 connections watching 537 paths
Total watches:6909

wchc

列出所有watcher信息,以watchersession为归组单元排列,列出该会话订阅了哪些pathNew in 3.3.0

$>echo wchc|nc localhost 2181
0x2389e662b97917f
/mytest/test/path1/node1
0x3389e65c83cd790
/mytest/test/path1/node2
0x1389e65c7ef6313
/mytest/test/path1/node3
/mytest/test/path1/node1

wchp

列出所有watcher信息,以watcherpath为归组单元排列,列出该path被哪些会话订阅着:New in 3.3.0

$>echo wchp|nc localhost 2181
/mytest/test/path1/node
0x1389e65c7eea4f5
0x1389e65c7ee2f68
/mytest/test/path1/node2
0x2389e662b967c29
/mytest/test/path1/node3
0x3389e65c83dd2e0
0x1389e65c7f0c37c
0x1389e65c7f0c364

注意,wchcwchp这两个命令执行的输出结果都是针对session的,对于运维人员来说可视化效果并不理想,可以尝试将cons命令执行输出的信息整合起来,就可以用客户端IP来代替会话ID了,具体可以看这个实现:http://rdc.taobao.com/team/jm/archives/1450

mntr

输出一些ZK运行时信息,通过对这些返回结果的解析,可以达到监控的效果。New in 3.4.0

$ echo mntr | nc localhost 2185
zk_version 3.4.0
zk_avg_latency 0
zk_max_latency 0
zk_min_latency 0
zk_packets_received 70
zk_packets_sent 69
zk_outstanding_requests 0
zk_server_state leader
zk_znode_count 4
zk_watch_count 0
zk_ephemerals_count 0
zk_approximate_data_size 27
zk_followers 4 – only exposed by the Leader
zk_synced_followers 4 – only exposed by the Leader
zk_pending_syncs 0 – only exposed by the Leader
zk_open_file_descriptor_count 23 – only available on Unix platforms
zk_max_file_descriptor_count 1024 – only available on Unix platforms

3.  适用场景

4.      应用实例

   ZooKeeper有了上述的这些用途,让我们设想一下,在一个分布式系统中有这这样的一个应用:

     2个任务工厂(Task Factory)一主一从,如果从的发现主的死了以后,从的就开始工作,他的工作就是向下面很多台代理(Agent)发送指令,让每台代理(Agent)获得不同的账户进行分布式并行计算,而每台代理(Agent)中将分配很多帐号,如果其中一台代理(Agent)死掉了,那么这台死掉的代理上的账户就不会继续工作了。

上述,出现了3个最主要的问题:

    1.Task Factory /从一致性的问题

    2.Task Factory /从心跳如何用简单+稳定 或者2者折中的方式实现。

    3.一台代理(Agent)死掉了以后,一部分的账户就无法继续工作,需要通知所有在线的代理(Agent)重新分配一次帐号。

5.      怕文字阐述的不够清楚,画了系统中的Task FactoryAgent的大概系统关系,如图所示:zookeeper apache

1.  OK,让我们想想ZooKeeper是不是能帮助我们去解决目前遇到的这3个最主要的问题呢?

解决思路

1. 任务工厂Task Factory都连接到ZooKeeper上,创建节点,设置对这个节点进行监控,监控方法例如:

    event= new WatchedEvent(EventType.NodeDeleted, KeeperState.SyncConnected, "/TaskFactory");

   这个方法的意思就是只要Task Factoryzookeeper断开连接后,这个节点就会被自动删除。

2.  2.原来主的任务工厂断开了TCP连接,这个被创建的/TaskFactory节点就不存在了,而且另外一个连接在上面的Task Factory可以立刻收到这个事件(Event),知道这个节点不存在了,也就是说主TaskFactory死了。

3.  3.接下来另外一个活着的TaskFactory会再次创建/TaskFactory节点,并且写入自己的ipznode里面,作为新的标记。

4.  4.此时Agents也会知道主的TaskFactory不工作了,为了防止系统中大量的抛出异常,他们将会先把自己手上的事情做完,然后挂起,等待收到Zookeeper上重新创建一个/TaskFactory节点,收到 EventType.NodeCreated 类型的事件将会继续工作。

5.  5.原来从的TaskFactory 将自己变成一个主TaskFactory,当系统管理员启动原来死掉的主的TaskFactory,世界又恢复平静了。

6.  6.如果一台代理死掉,其他代理他们将会先把自己手上的事情做完,然后挂起,向TaskFactory发送请求,TaskFactory会重新分配(sharding)帐户到每个Agent上了,继续工作。

上述内容,大致如图所示: zookeeper apache

附件下载:

分享到:0
关注微信,跟着我们扩展技术视野。每天推送IT新技术文章,每周聚焦一门新技术。微信二维码如下:
微信公众账号:尚学堂(微信号:bjsxt-java)
声明:博客文章版权属于原创作者,受法律保护。如果侵犯了您的权利,请联系管理员,我们将及时删除!
(邮箱:webmaster#sxt.cn(#换为@))
北京总部地址:北京市海淀区西三旗桥东建材城西路85号神州科技园B座三层尚学堂 咨询电话:400-009-1906 010-56233821
Copyright 2007-2015 北京尚学堂科技有限公司 京ICP备13018289号-1 京公网安备11010802015183