vlambda博客
学习文章列表

都0202年了,你确定还不学一波Zookeeper?

     Zookeeper的应用场景有很多,下面会分别介配置中心、命名服务、集群管理、分布式锁、分布式队列等场景



      Zookeeper这个词语相信大家已经或多或少听过了,不知道大家对它了解多少呢?这一篇通过数据模型Znode、Watcher机制 、读写数据流程、leader 选举(脑裂问题等)、数据一致性与paxos 算法以及各种应用场景等几方面来系统性的让你快速学习Zookeeper!

Zookeeper是一个分布式系统的可靠协调系统。先来跟湿兄看张图:


都0202年了,你确定还不学一波Zookeeper?


     在Hadoop(一个大数据处理框架)的生态体系中,存在很多的服务或组件(比如hive、pig等),每个服务和组件之间的协调处理是一件很麻烦的事情,需要高性能的一致性协调框架,于是Zookeeper诞生了。


1、介绍ZooKeeper


    ZooKeeper到底是什么?我们先来看官网介绍:


都0202年了,你确定还不学一波Zookeeper?


简单概括下:


    ZooKeeper主要服务于分布式系统,可以用ZooKeeper来做:统一配置管理、统一命名服务、分布式锁、集群管理。本质上是一个小文件存储系统,提供基于目录树方式的数据存储,Znode是一个跟Unix文件系统路径相似的节点,可以往这个节点存储或获取数据,并且可以对节点的数据进行维护和监控。


   ZooKeeper集群中的每一个server保存着相同的数据副本,无论client连接哪个server,展示的数据都是一致的。使用分布式系统无法避免对节点的管理问题(实时感知节点状态,节点统一管理),这些问题在分布式系统中提高复杂性,ZooKeeper作为一个中间件应运而生。


了解下ZooKeeper名字:

       关于“ZooKeeper”这个项目的名字,其实也有一段趣闻。在立项初期,考虑到之前内部很多项目都是使用动物的名字来命名的(例如著名的Pig项目),雅虎的工程师希望给这个项目也取一个动物的名字。时任研究院的首席科学家 RaghuRamakrishnan 开玩笑地说:“再这样下去,我们这儿就变成动物园了!”此话一出,大家纷纷表示就叫动物园管理员吧一一一因为各个以动物命名的分布式组件放在一起,雅虎的整个分布式系统看上去就像一个大型的动物园了,而 Zookeeper 正好要用来进行分布式环境的协调一一于是,Zookeeper 的名字也就由此诞生了。

都0202年了,你确定还不学一波Zookeeper?


2、数据模型Znode


   先跟湿兄理解ZooKeeper的空间结构:


都0202年了,你确定还不学一波Zookeeper?


    ZooKeeper拥有一个层次的命名空间,这个和标准的文件系统非常相似,都是采用树形层次结构,ZooKeeper树中的每个节点被称为Znode。Znode分为两种类型:


   短暂/临时(Ephemeral):当客户端和服务端断开连接后,所创建的Znode(节点)会自动删除,生命周期依赖于会话(可手动删除,不允许有子节点)。在集群下虽然临时节点会绑定一个会话,但是对所有客户端可见。

    持久(Persistent):当客户端和服务端断开连接后,所创建的Znode(节点)不会删除。只有客户端执行删除操作,才会被删除。


                              都0202年了,你确定还不学一波Zookeeper?


接着来看下Znode结构:


    Znode,兼具文件和目录两种特点。既像文件一样维护着元数据等信息,又像目录一样可以作为路径标识的一部分,上图中的每个节点都为一个Znode。每个Znode由3部分组成:

  • stat:此为状态信息, 描述该Znode的版本, 权限等信息;

  • data:与该Znode关联的数据;

  • children:该Znode下的子节点;


    Znode存储数据的时候每个Znode最多存储1M的数据,平时使用中一般远小于此值;当创建Znode的时候,用户可以请求在ZooKeeper的路径结尾添加一个递增的计数。这个计数对于此节点的父节点来说是唯一的,它的格式为”%10d”(10位数字,没有数值的数位用0补充,例如”0000000001″)。当计数值大于2的32次幂-1时,计数器将溢出。


接下来我们一起看常用命令:

                            都0202年了,你确定还不学一波Zookeeper?


connect host:port:如connect 127.0.0.1:2181,连接zk服务端,与close命令配合使用可以连接或者断开zk服务端。
create [-s] [-e] path data acl:- s参数为顺序节点,-e参数为临时节点。
stat path [watch]:查看节点状态信息,watch参数为加上监视器为这个节点。
set path data [version]: 设置节点的数据,如set /zookeeper "hello world"。同时为这个节点加监视器。
ls path [watch]:获取路径下的节点信息,注意此路径为绝对路径,类似于linux的ls命令,如ls /zookeeper。watch参数加监视器。
ls2 path [watch]:ls2为ls命令的扩展,比ls命令多输出本节点信息。
get path [watch]:获取节点信息,注意节点的路径皆为绝对路径,也就是说必要要从/(根路径)开始。watch参数为是否加监视器(下面讲解)。
delete path [version]:删除节点命令,如delete /zknode 。
rmr path:删除节点命令,此命令与delete命令不同的是delete不可删除有子节点的节点,但是rmr命令可以删除,注意路径为绝对路径。如rmr   /zookeeper/znode。
printwatches on|off:设置和显示监视状态,on或者off。如printwatches on
history :显示命令历史;
close :断开连接;
quit:退出客户端;


    节点不仅可以存储信息,还可以利用Watcher机制同时监视该节点的状态,来达到动态感知的效果。


3、Watcher机制


      ZooKeeper采用了Watcher机制实现数据的发布/订阅功能,该机制在被订阅对象发生变化时会异步通知客户端,因此客户端不必在Watcher注册后轮询阻塞,从而减轻了客户端压力。Watcher机制实际上和观察者模式类似,也可以看作是观察者模式在分布式下的实现。


    我们可以在Znode上注册Watcher,在该节点发生变化或者其子节点发生变化时触发监听事件,服务器通知到客户端。


                            都0202年了,你确定还不学一波Zookeeper?


实现原理:Watcher实现由三个部分组成


  • Zookeeper服务端;

  • Zookeeper客户端;

  • 客户端的ZKWatchManager对象;


     客户端首先将Watcher注册到服务端,同时将Watcher对象保存到客户端的Watcher管理器中。当ZooKeeper服务端监听的数据状态发生变化时,服务端会主动通知客户端,接着客户端的Watch管理器会触发相关Watcher来回调相应处理逻辑,从而完成整体的数据发布/订阅流程。

都0202年了,你确定还不学一波Zookeeper?


一些特性:


      Watcher是一次性的,一旦被触发就会移除,再次使用时需要重新注册;客户端的Watcher回调是顺序串行化执行的,只有回调后客户端才能看到最新的数据状态。一个Watcher回调逻辑不应该太多,以免影响别的watcher执行;

     WatchEvent是最小的通信单元,结构上只包含通知状态、事件类型和节点路径,并不会告诉数据节点变化前后的具体内容;Watcher只有在当前session彻底失效时才会无效,若在session有效期内快速重连成功,则watcher依然存在,仍可接收到通知;


     ZooKeeper的监听机制常应用于两个场景:监听Znode节点的数据变化以及监听子节点的增减变化。


4、读写数据流程


      介绍读写流程之前,需要先介绍ZooKeeper集群中的角色扮演(Leader、Follower、Observer):


                              都0202年了,你确定还不学一波Zookeeper?

Leader角色:


   整个 zookeeper 集群的核心,事务请求的唯一调度和处理者,保证集群事务的处理的顺序性。集群内部各服务器的调度者。


Follower角色:


    处理客户端的读请求,转发事务请求(写请求等)给Leader服务器。参与Leader发起的提案的投票,需要半数以上follower节点通过,leader才会commit数据。参与Leader选举(原Leader崩溃,需要重新选举)。


Observer角色


    Observer 是 zookeeper3.3 开始引入的一个全新的服务器角色,观察 zookeeper 集群中的最新状态变化并将这些状态变化同步到 observer 服务器上。Observer 的工作原理与 follower 角色基本一致,而它和 follower 角色唯一的不同在于 observer 不参与任何形式的投票。


问题:为何引入Observer角色?


     很简单,思考下,随着zk集群的server增加,集群的写性能逐渐下降(因为Znode的变更需要半数投票通过),Observer是一种新型的Zookeeper节点,可以帮助解决上述问题,提供Zookeeper的可扩展性。Observer不参与投票,只是简单的接收投票结果,因此我们增加再多的Observer,也不会影响集群的写性能。


Observer的一大优势:


      因为Observer不参与投票,所以他们不属于Zookeeper集群的关键部位,即使他们Failed,或者从集群中断开,也不会影响集群的可用性。


       根据Observer的特点,我们可以使用Observer作跨数据中心部署。如果将Leader和Follower分割到多个数据中心的话,因为数据中心之间的网络延迟,会导致集群性能的下降。使用Observer则可以解决这个问题,将Observer跨机房部署,而Leader和Follower部署在同一个数据中心,这样更新操作便会由一个中心来解决处理,并将数据发送到其它数据中心。


     当然使用Observer并非会完全消除网络延迟,因为Observer还是需要接收Leader的同步结果,以及自身的更新请求也需要发送给Leader上,优势就是本地的读请求会非常快。

   在对应的配置文件加peerType=observer即可。


读写数据流程


     我们以三台zookeeper服务器的集群为例,一个Leader,两个follower:


都0202年了,你确定还不学一波Zookeeper?


   写流程:


1. Client向Server端发起写请求,检查是否是Leader,不是则转发至Leader;(来自客户端的所有写请求都要转发至Leader)
2. Leader根据该请求发起一个Proposal提议,其它的Server对该请求进行Vote(投票);
3. Leader收集投票,当Vote数量过半时Leader会向所有的Server发送一个通知消息,所有Server进行更新;(可能包括Observer)
4. 当Client所连接的Server收到该消息时,会把该操作更新到内存中并对Client的写请求做出回应。

      当我们需要增加ZK服务中Client数量时,那我们就需要增加Server的数量,增加了之后对上述中的投票过程造成压力,随着ZK集群变大,写操作的吞吐量下降。于是我们就有了Observer这一不参与投票的服务器, Observer可以接受客户端的连接,并将写请求转发给Leader节点。但是,Leader节点不会要求 Observer参加投票,仅像上述第三步收到投票结果,更新数据。


读流程:


    ZooKeeper服务中的每个Server可服务于多个Client,并且Client可连接到ZK服务中的任一台Server来提交请求。若是读请求,则由每台Server的本地副本数据库直接响应。


5、leader 选举


      写流程中涉及到了投票机制去决定一个Leader发起的一个提议,但是如果Leader在ZK集群挂掉怎么办?或者说集群初始化的leader如何选举出来?


在介绍选举之前先看几个概念:


myid:服务器SID,一个数字,通过配置文件配置,是唯一的。
ZXID(zookeeper transaction id):每个改变Zookeeper状态的操作都会形成一个对应的zxid,并记录到transaction log中。这个值越大,表示更新越新。

选举中的几个状态:


LOOKING,竞选状态。
FOLLOWING,随从状态,同步leader状态,参与投票。
OBSERVING,观察状态,同步leader状态,不参与投票。
LEADING,领导者状态。


leader 选举分为两种:服务器启动时的leader选举和服务器运行时期的leader选举;


    服务器启动时:当集群初始化时,有一台服务器Server1启动时,是无法进行Leader选举的,当第二台Server2启动后,两台服务器可以相互通信,都试图找到Leader,进入选举过程。接下来跟着湿兄一起看


1. 每个Server发出一个投票。初始情况每一个Server都会投票给自己来做Leader服务器,每次投票会包含所推举服务器的myid和ZXID,此时Server1投票为(1,0),Server2为(2,0),然后将投票信息发送到集群中其它服务器。
2. 接受其它服务器的投票信息。收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自LOOKING状态的服务器。
3. 处理投票信息。服务器需要将每一个投票和自己的投票进行对比,对比规则是ZXID大的作为Leader,如果ZXID相同则myid大的做Leader。对于Server1和Server2来说,ZXID相同都是0,Server2的myid大,于是server1更新自己的投票信息为(2,0)。于是重新投票,再次向集群中所有机器发出上一次投票信息即可。
4. 统计投票信息。每次投票后,服务器统计投票数据,判断是否有过半的机器收到相同的投票,若某个投票达到一半的要求,则认为该投票提出者可以成为Leader。此时Server2称为Leader。
5. 改变服务器的状态。Leader确定,服务器更新本地状态,是Follower就变更为FOLLOWING,是Leader就变更为LEADING。


服务器运行时期(Leader宕机):在ZK集群中,非Leader的服务器宕机或者新加入的时候,是不会影响集群中的Leader的,也就是不会选举新Leader。但是<font color=red size=4 >一旦Leader挂了</font>呢,那么整个集群将暂停对外服务,进行新一轮的Leader选举,和启动时Leader选举基本一致。(下面五个步骤的后四个和启动选举基本一致,不再多说)


1.变更状态。Leader挂后,余下的非Observer服务器都会将自己的服务器状态变更为LOOKING,然后开始进入Leader选举流程。
2.每个Server发出一个投票。
3. 处理投票信息。
4. 统计投票信息。
5. 改变服务器的状态。


  其实选举过程还是很好理解的,不理解的去挠个头,就明白了

                                     都0202年了,你确定还不学一波Zookeeper?


问题:ZK集群节点为什么要部署成奇数?


    ZK集群有这么一个特性:集群中只要有过半的机器正常工作,那么整个集群对外是可用的。先说结论:这样是为了在最大容错服务器个数的条件下,能够节省资源。


     接下来我们来分析,一个集群有5个zookeeper节点机器最多宕掉2台,还可以继续使用,因为剩下3台大于5/2。比如要求最大容错服务器是2的情况下,对应的集群是5时满足条件,对应的集群节点是6时也是2(宕机3个的时候,剩余节点无法过半,集群崩溃),所以从节约资源的角度看,没必要部署6(偶数)个zookeeper服务节点。


问题:分布式系统中的脑裂问题。


      在机房1中部署Server1、2、3,机房2中部署Server4、5、6,6台Server组成一个集群,正常情况下这个集群存在一个Leader,但是如果机房之间的网络断掉,但是机房内的网络还是通的,如果不考虑过半机制,那么便会成为两个集群,选举出两个Leader,即为“脑裂”。此时机房1和2恢复连接,问题出现了,两个集群刚刚都对外提供服务了,数据该怎么合并,数据冲突怎么解决等等问题。


                                    都0202年了,你确定还不学一波Zookeeper?


问题:如何避免脑裂?


1、Quorums (法定人数) 方式:设置法定人数,即最少存活节点个数。比如4个节点的集群,Quorums = 3,集群容忍度是1,若两个节点失效,则集群失效。zookeeper的默认采用方式。
2、Redundant communications (冗余通信)方式:集群中采用多种通信方式,提高容错性。
3、Fencing (共享资源) 方式:设置共享资源,能看到共享资源的就在集群中,能获得锁的是Leader,看不到资源不在集群中。


问题:为何集群一定要过半?


    因为这样不需要等待所有Server都投了同一个Server就可以选举出来一个Leader了,这样比较快,即快速领导者选举算法,而且也可以提高集群的容错性。


6、数据一致性与paxos 算法


    数据一致性问题,是指数据在多个副本之间保持一致的特性。分布式环境里,多个副本处于不同的节点上,如果对副本A的更新操作,未同步到副本B上,外界获取数据时,A与B的返回结果会不一样,这是典型的分布式数据不一致情况。


    而强一致性,是指分布式系统中,如果某个数据更新成功,则所有用户都能读取到最新的值。最终一致性,则要求不那么严格,保证数据最终更新到每一个Server即可。


                              都0202年了,你确定还不学一波Zookeeper?


paxos 算法


     Paxos算法是一种基于消息传递的一致性算法。Paxos有一个前提:没有 拜占庭将军问题。就是说Paxos只有在一个可信的计算环境中才能成立,这个环境是不会被入侵所破坏的。


     Paxos算法需要解决的问题就是如何在一个可能发生诸如机器宕机或网络异常等问题的分布式系统中,快速且正确地在集群内部对某个数据的值达成一致。也可以理解成分布式系统中达成状态的一致性。

     这个算法很有深度,感兴趣的可以自己去研究,湿兄在这里不介绍了。推荐书籍:《从Paxos到Zookeeper分布式一致性原理与实践》


ZAB


       Zookeeper并未完全采用Paxos算法,而是采用了一种ZAB的协议,该协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性,同时其崩溃恢复过程也确保看zk集群的高可用性(HA)。


ZAB 协议包括两种基本的模式:


崩溃恢复:系统启动或者leader宕机即进入崩溃恢复模式,开始新一轮选举,选举产生的leader会与过半的follower进行同步,使数据一致,当与过半的机器同步完成后,就退出恢复模式,进入消息广播模式。
消息广播:当集群中已经有过半的follower与leader服务器完成了状态同步,那么整个zk集群就可以进入消息广播模式了。


7、应用场景


     Zookeeper的应用场景有很多,下面会分别介配置中心、命名服务、集群管理、分布式锁、分布式队列等场景


配置中心


     比如现在我们有A、B、C三个系统,它们三个系统的配置文件分别是A.yml、B.yml和C.yml,然后,这三份配置又非常类似,很多的配置项几乎都一样。此时我们想改修改其中一份配置项信息,可能其余两个配置文件也要修改,并且,改变了配置项的信息很可能就要重启系统。

   

    改进:我们将三个配置文件的公共部分抽取出来comon.yml,把这个配置文件放在Zookeeper的Znode中,系统A、B、C同时监听这个节点,一旦变更,及时响应即可。


命名服务


       统一命名服务的理解其实跟域名一样,是我们为这某一部分的资源给它取一个名字,别人通过这个名字就可以拿到对应的资源,类似于一种Key-Value的映射关系。比如说,现在我有一个域名www.dashixiong.com,但我这个域名下有多台机器:


192.168.1.1
192.168.1.2
192.168.1.3
192.168.1.4


    于是使用者访问www.dashixiong.com即可访问到我的机器,而不是通过IP去访问。


集群管理


      分布式环境中,我们需要实时掌握每个节点的状态,可以将信息写入到临时顺序节点中去,并监听该节点,一旦节点崩溃,便可以感知到变化。(和下面分布式锁一样道理)


分布式锁


    分布式锁可以说是很重要的功能了,也是面试热点。我们可以使用ZooKeeper来实现分布式锁,那是怎么做的呢?



      系统A、B、C都去访问/locks节点,访问的时候都创建顺序的临时节点,类似上图所示,系统A创建了id_000000节点,系统B创建了id_000002节点,系统C创建了id_000001节点。紧接着,再拿到/locks下的所有子节点信息(id_000000,id_000001,id_000002),判断自己是否是最小的子节点。


是的话,则拿到分布式锁,执行完之后释放锁,删除掉这个临时节点。
不是的话,则监听比自己小1的节点变化。


举个例子:

  1. 系统A拿到/locks下的所有子节点,经过比较发现自己子节点id_000000最小,于是拿到分布式锁。                                                                                                       2. 系统B同样操作,发现自己id_000002不是最小节点,于是监听id_000001的节点的状态。                                                                                                                    3. 系统C同样,发现自己id_000001不是最小的,于是监听id_000000这个比自己小1的节点的状态。                                                                                                        4. 等待系统A执行完了,节点id_000000删除,系统C的id_000001感知到自己是最小的节点了,于是拿到分布式锁操作。


分布式队列


     如果大家理解队列的话,也很容易理解分布式队列的实现了。道理和上面的分布式锁一个道理,都是通过临时顺序节点来创建,不再一一赘述。


     总结:zookeeper是通过Znode的节点类型和监听机制实现了很多功能,这是zookeeper的基础,同时也是重点。


絮叨


    你知道的越多,你不知道的也越多。


    不错哦,又学习了新知识ZooKeeper,在面试中又可以多吹几分钟了,觉得不错的可以给大湿兄一个关注,也欢迎可爱帅气的你推荐给你的朋友,转发和点赞可以给湿兄多打打气~~