ZooKeeper工作原理

ZooKeeper

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务应用。

其实就是用于保存一些分布式系统中的服务器与服务器之间进行同步的一些信息,这些信息可以被所有以ZooKeeper
客户端API方式访问的客户端(也是服务器)进行访问.

ZooKeeper的作用可以这么去理解,所有要进行分布式的系统(多个服务器进行集群),它们将一些同步的信息保存到
ZooKeeper服务器的一个位置(Znode),分布式的系统的所有服务器都与ZooKeeper服务器以长连接方式进行通信。


举个简单的例子:
一个客户端(服务器)与读取集群中的数据,但集群是由多个服务器组成的(不有所有服务器的数据都是一样的,只
有主是最新的,从的可能还没有更新),那么客户端就不知道去集群中的那个服务器读取数据了(当然可以向每个都
文都读一次,但这效率就低了,这种方式不可用)。为解决这个问题可以引一个中间件,这个中间件会在集群中选出
一个主服务器,并保存主服务器的信息(如IP、端口号等),客户端同样会连接这个中间件,并可以得到中间件保存主
服务器的信息(如IP、端口号等),这样客户端就可以知道与集群中的那个服务器进行传输数据了。ZooKeeper就相当
于这种中间件的作用.

中间件内容的变化会通知到所有与其连接的所有客户端(客户端、集群中的所有服务器)进行信息更新,这个客户端
的概念是以中间件作为服务器来说的,其实他们也是服务器.



ZooKeeper数据模型
1.ZooKeeper拥有一个层次的命名空间,这个和分布式的文件系统非常相似。
2.不同的是ZooKeeper命名空间中的Znode,兼具文件和目录两种特点。既像文件一样维护着数据、元信息、ACL、时间
戳等数据结构,又像目录一样可以作为路径标识的一部分,并可以具有子znode。
3.用户对znode具有增、删、改、查等操作(权限允许的情况下)。
4.就是ZooKeeper保存数据是以一个Znode的方式来存储的,Znode包含了数据和元数据(Znode的表示信息,后面会说到)
5.znode具有原子性操作,每个znode的数据将被原子性地读写,读操作会读取与znode相关的所有数据,写操作会一次性
替换所有数据。znode的数据大小默认为1M(可以进行配置),一般不会保存大量的数据




图中的每个节点称为一个znode. 每个znode由3部分组成:
1.stat:此为状态信息, 描述该znode的版本, 权限等信息.
2.data:与该znode关联的数据.
3.children:该znode下的子节点.



zookeeper存储之文件格式分析
1.zookeeper主要存放了两类文件,一个是snapshot(内存快照)和log(zookeeper操作记录),前者是内存数的快照,后者
类似mysql的binlog,将所有与修改数据相关的操作记录在log中


snapshot(内存快照)
就是将内存中那些znode的数据(包括znode的数据、元数据)都保存一份到磁盘上。方便重启后可以从快照内容恢复znode
的内存结构信息。

参考原文: http://blog.csdn.net/pwlazy/article/details/8080626


log
所有与修改数据相关的操作记录,这是一个对zookeeper操作的日志记录文件,zookeeper中znode的增、删、改、查操作的日
志都会保存到这个log中。



ZooKeeper Client API(与ZooKeeper server通信):
1.create(path, data, flags): 创建一个ZNode, path是其路径,data是要存储在该ZNode上的数据,flags常用的有: PERSISTEN,
PERSISTENT_SEQUENTAIL, EPHEMERAL, EPHEMERAL_SEQUENTAIL
2.delete(path, version): 删除一个ZNode,可以通过version删除指定的版本, 如果version是-1的话,表示删除所有的版本
3.exists(path, watch): 判断指定ZNode是否存在,并设置是否Watch这个ZNode。这里如果要设置Watcher的话,Watcher是在创
建ZooKeeper实例时指定的,如果要设置特定的Watcher的话,可以调用另一个重载版本的exists(path, watcher)。
4.getData(path, watch): 读取指定ZNode上的数据,并设置是否watch这个ZNode
5.setData(path, watch): 更新指定ZNode的数据,并设置是否Watch这个ZNode
6.getChildren(path, watch): 获取指定ZNode的所有子ZNode的名字,并设置是否Watch这个ZNode
7.sync(path): 把所有在sync之前的更新操作都进行同步,达到每个请求都在半数以上的ZooKeeper Server上生效。path参数目前没有用
8.setAcl(path, acl): 设置指定ZNode的Acl信息
9.getAcl(path): 获取指定ZNode的Acl信息

watch就是是否设置一个对这个ZNode的监控,一旦这个ZNode发生了变化(子ZNode、自身、数据、更新等变化)都会发送通知到
你(读取这个ZNode的客户端,让这个客户端作出相应的处理)



ZooKeeper Session
客户端与ZooKeeper是以心跳的方式进行连接状态的判断的。每个客户端一旦连接上了ZooKeeper,就可以在ZooKeeper上创建一个
属于自己的ZNode(但不一定需要)。



ZooKeeper Watch
1.Zookeeper watch是一种监听通知机制。
2.监视事件可以理解为一次性的触发器,当设置监视的数据(ZNode)发生改变时,该监视事件会被发送到客户端.例如,如果客户端
调用了getData("/znode1", true) 并且稍后 /znode1 节点上的数据发生了改变或者被删除了,客户端将会获取到 /znode1 发生
变化的监视事件,而如果 /znode1 再一次发生了变化,除非客户端再次对/znode1 设置监视,否则客户端不会收到事件通知(一次性)。



zookeeper集群(防止一个zookeeper的宕机)
在zookeeper集群中(就是zookeeper服务器自已的集群(zookeeper也有可能会down的)),各个节点(客户端)共有下面3种角色和4种状态:
1.角色:leader,follower,observer
2.状态:leading,following,observing,looking


ZooKeeper集群的工作原理


1.zk(就是那个znode)进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的Server都恢复到一个正确的状态。
2.这里要保存的数据当然就是znode树的相关数据了
3.各个zookeeper服务器之间是相互通信的,当客户端向其中一个zookeeper服务器更新数据时(增加znode、更改znode数据),follower
会把这个事务提交到leader,leader广播这个事务给zookeeper集群,收到这个事务的每follower就更新自己的数据(znode树的相关数据),
并回复leader应答,leader接收每follower的应答,当有一半以上的follower正确应答了,就认为这个事务提交成功,向客户端回复数据
更新状态。当然leader监控到某个znode有状态改也会广播这个事务给zookeeper集群.

就是用ZooKeeper这个应用来达到分布式数据一致性的(当然是弱一致性),当超过一半的数据服务器更新了数据就认为是达到了一致性。
当然应用要得到最新的数据(这个数据服务器的数据可以不是最新的)也可以通过sync()方法来得到最新的数据。ZooKeeper是以ZAB协议
来进行达到一致性的.

客户端是要知道每个ZooKeeper服务器的IP的,以防一个访问不了转去访问另一个,也可以只知道一个,看使用场境要求。


Leader Election(选主原理)
1.因为leader每次提交事务的时候都会带有zxid(可以理解为一个事务ID)等相关在信息,zxid每次都会增加1,leader和follower的log中
也会有这些信息,每次发生leader改变时zxid都会从0开始。
2.因为有了这个zxid,zxid越大表示数据(znode树的相关数据)最新,所以当leader宕机后会从follower中选举出zxid大的follower作
为新leader,其他follower更新到与leader相同的数据。



zookeeper应用
1.配置管理(共享配置信息)
比如将APP1的所有配置配置到/APP1 znode下,APP1所有机器一启动就对/APP1这个节点进行监控(zk.exist(“/APP1″,true)),并且实现
回调方法 Watcher,那么在zookeeper上/APP1 znode节点下数据发生变化的时候,每个机器都会收到通知,Watcher方法将会被执行,
那么应用再取下数据即可 (zk.getData(“/APP1″,false,null));


2.集群管理
应用集群中,我们常常需要让每一个机器知道集群中(或依赖的其他某一个集群)哪些机器是活着的,并且在集群机器因为宕机,网络
断链等原因能够不在人工介入的情况下迅速通知到每一个机器。

Zookeeper同样很容易实现这个功能,比如我在zookeeper服务器端有一个znode叫/APP1SERVERS,那么集群中每一个机器启动的时候都去
这个节点下创建一个EPHEMERAL类型的节点,比如server1创建/APP1SERVERS/SERVER1(可以使用ip,保证不重复),server2创建
/APP1SERVERS/SERVER2,然后SERVER1和SERVER2都watch /APP1SERVERS这个父节点,那么也就是这个父节点下数据或者子节点变化都会
通知对该节点进行watch的客户端。因为EPHEMERAL类型节点有一个很重要的特性,就是客户端和服务器端连接断掉或者session过期就
会使节点消失,那么在某一个机器挂掉或者断链的时候,其对应的节点就会消失,然后集群中所有对/APP1SERVERS进行watch的客户端
都会收到通知,然后取得最新列表即可。

另外有一个应用场景就是集群选master,一旦master挂掉能够马上能从slave中选出一个master,实现步骤和前者一样,只是机器在启动
的时候在APP1SERVERS创建的节点类型变为EPHEMERAL_SEQUENTIAL类型,这样每个节点会自动被编号.

我们默认规定编号最小的为master,所以当我们对/APP1SERVERS节点做监控的时候,得到服务器列表,只要所有集群机器逻辑认为最小
编号节点为master,那么master就被选出,而这个master宕机的时候,相应的znode会消失,然后新的服务器列表就被推送到客户端,
然后每个节点逻辑认为最小编号节点为master,这样就做到动态master选举。(这里的master与上面的leader不是同一码事,这里的
master是为其他应用做的,如每个客户端是一直要给master发数据的,就要知道master的相关信息了(如IP等))


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


4.队列管理
Zookeeper 可以处理两种类型的队列:当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列;
队列按照 FIFO 方式进行入队和出队操作,例如实现生产者和消费者模型

创建一个父目录 /synchronizing,每个成员都监控目录 /synchronizing/start 是否存在,然后每个成员都加入这个队列(创建
/synchronizing/member_i 的临时目录节点),然后每个成员获取 / synchronizing 目录的所有目录节点,判断 i 的值是否已经是
成员的个数,如果小于成员个数等待 /synchronizing/start 的出现,如果已经相等就创建 /synchronizing/start。

参考原文: http://blog.csdn.net/l1028386804/article/details/52226265



总结:
Zookeeper就是以目录树这样的方式来共享一些信息,并在信息变化时通知到与它有连接的所有Zookeeper客户端,让客户端得到共享
的信息和知道信息的改变,从而让客户端做出相应的处理.

猜你喜欢

转载自huangyongxing310.iteye.com/blog/2323576