赞
踩
ZooKeeper是一个典型的分布式数据一致性的解决方案,分布式应用程序可以基于它实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调通知、集群管理、 Master选举、分布式锁和分布式队列等功能。
ZooKeeper可以保证如下分布式一致性特性。
通常在分布式系统中,构成一个集群的每一台机器都有自己的角色,最典型的集群模式就是 Master/Slave模式(主备模式)。在这种模式中,我们把能够处理所有写操作的机器称为 Master机器,把所有通过异步复制方式获取最新数据,并提供读服务的机器称为Slave机器。
而在 ZooKeeper中,这些概念被颠覆了。它没有沿用传统的 Master/Slave概念,而是引入了Leader、 Follower和 Observer三种角色。 ZooKeeper集群中的所有机器通过一个Leader选举过程来选定一台被称为“Leader”的机器, Leader服务器为客户端提供读和写服务。除 Leader外,其他机器包括 Follower和 Observer。 Follower和 Observer都能够提供读服务,唯一的区别在于, Observer机器不参与 Leader选举过程,也不参与写操作的“过半写成功”策略,因此 Observer可以在不影响写性能的情况下提升集群的读性能。
Session是指客户端会话,在讲解会话之前,我们首先来了解一下客户端连接。在Zooκeper中,一个客户端连接是指客户端和服务器之间的一个TCP长连接。ZooKeeper对外的服务端口默认是2181,客户端启动的时候,首先会与服务器建立一个TCP连接从第一次连接建立开始,客户端会话的生命周期也开始了,通过这个连接,客户端能够通过心跳检测与服务器保持有效的会话,也能够向ZooKeeper服务器发送请求并接受响应,同时还能够通过该连接接收来自服务器的 Watch事件通知。Session的 sessionTimeout值用来设置一个客户端会话的超时时间。当由于服务器压力太大、网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时,只要在 sessionTimeout规定的时间内能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效。
在谈到分布式的时候,我们通常说的“节点”是指组成集群的每一台机器。然而,在ZooKeeper中,“节点”分为两类,第一类同样是指构成集群的机器,我们称之为机器节点;第二类则是指数据模型中的数据单元,我们称之为数据节点ZNode。 ZooKeeper将所有数据存储在内存中,数据模型是一棵树(ZNode Tree),由斜杠(/)进行分割的路径,就是一个 Znode,例如/foo/path/。每个 ZNode上都会保存自己的数据内容,同时还会保存一系列属性信息。
在 ZooKeeper中, ZNode可以分为持久节点和临时节点两类。所谓持久节点是指一旦这个 ZNode被创建了,除非主动进行 ZNode的移除操作,否则这个 ZNode将一直保存在ZooKeeper上。而临时节点就不一样了,它的生命周期和客户端会话绑定,一旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。另外, ZooKeeper还允许用户为毎个节点添加一个特殊的属性:SEQUENTIAL。一旦节点被标记上这个属性,那么在这个节点被创建的时候, ZooKeeper会自动在其节点名后面追加上一个整型数字,这个整型数字是一个由父节点维护的自增数字。
ZooKeeper的每个ZNode上都会存储数据,对应于每个 ZNode,ZooKeeper都会为其维护一个叫作Stat的数据结构,Stat中记录了这个 ZNode的三个数据版本,分别是 version(当前 ZNode的版本)、 cversion(当前 ZNode子节点的版本)和aversion(当前 ZNode的ACL版本)。
Watcher(事件监听器),是 ZooKeeper中的一个很重要的特性。 ZooKeeper允许用户在指定节点上注册一些 Watcher,并且在一些特定事件触发的时候, ZooKeeper服务端会将事件通知到感兴趣的客户端上去,该机制是 ZooKeeper实现分布式协调服务的重要特性。
ZooKeeper采用ACL(Access Control lists)策略来进行权限控制,类似于UNIX文件系统的权限控制。 ZooKeeper定义了如下5种权限。
其中尤其需要注意的是, CREATE和 DELETE这两种权限都是针对子节点的权限控制。
很多人都会认为 ZooKeeper就是 Paxos算法的一个实现但事实上, ZooKeeper并没有完全采用 Paxos算法,而是使用了一种称为 ZooKeeper Atomic broadcast(ZAB, ZooKeeper原子消息广播协议)的协议作为其数据一致性的核心算法。
ZAB协议是为分布式协调服务 ZooKeeper专门设计的一种支持崩溃恢复的原子广播协议。它是一种特别为 ZooKeeper设计的崩溃可恢复的原子消息广播算法。
在ZooKeeper中,主要依赖ZAB协议来实现分布式数据一致性,基于该协议, ZooKeeper实现了一种主备模式的系统架构来保持集群中各副本之间数据的一致性。具体的,ZooKeeper使用一个单一的主进程来接收并处理客户端的所有事务请求,并采用ZAB的原子广播协议,将服务器数据的状态变更以事务 Proposal的形式广播到所有的副本进程上去。ZAB协议的这个主备模型架构保证了同一时刻集群中只能够有一个主进程来广播服务器的状态变更,因此能够很好地处理客户端大量的并发请求。另一方面,考虑到在分布式环境中,顺序执行的一些状态变更其前后会存在一定的依赖关系,有些状态变更必须依赖于比它早生成的那些状态变更,例如变更C需要依赖变更A和变更B。这样的依赖关系也对ZAB协议提出了一个要求:ZAB协议必须能够保证一个全局的变更序列被顺序应用,也就是说,ZAB协议需要保证如果一个状态变更已经被处理了,那么所有其依赖的状态变更都应该已经被提前处理掉了。最后,考虑到主进程在任何时候都有可能出现崩溃退出或重启现象,因此,ZAB协议还需要做到在当前主进程出现上述异常情况的时候,依旧能够正常工作。
ZAB协议的核心是定义了对于那些会改变 ZooKeeper服务器数据状态的事务请求的处理方式,即:
所有事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被称为 Leader服务器,而余下的其他服务器则成为 Follower服务器。 Leader服务器负责将一个客户端事务请求转换成一个事务 Proposal(提议),并将该 Proposal分发給集群中所有的Follower服务器。之后 Leader服务器需要等待所有 Follower服务器的反馈,一旦超过半数的 Follower服务器进行了正确的反馈后,那么 Leader就会再次向所有的 Follower服务器分发 Commit消息,要求其将前一个 Proposal进行提交。
ZAB协议包括两种基本的模式,分别是崩溃恢复和消息广播。当整个服务框架在启动过程中,或是当 Leader服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB协议就会进入恢复模式并选举产生新的 Leader服务器。当选举产生了新的Leader服务器,同时集群中已经有过半的机器与该 Leader服务器完成了状态同步之后,ZAB协议就会退出恢复模式。其中,所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和 Leader服务器的数据状态保持一致。
当集群中已经有过半的 Follower服务器完成了和 Leader服务器的状态同步,那么整个服务框架就可以进入消息广播模式了。当一台同样遵守ZAB协议的服务器启动后加入到集群中时,如果此时集群中已经存在一个 Leader服务器在负责进行消息广播,那么新加入的服务器就会自觉地进入数据恢复模式:找到 Leader所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。正如上文介绍中所说的, ZooKeeper设计成只允许唯一的一个 Leader服务器来进行事务请求的处理。 Leader服务器在接收到客户端的事务请求后,会生成对应的事务提案并发起一轮广播协议;而如果集群中的其他机器接收到客户端的事务请求,那么这些非 Leader服务器会首先将这个事务请求转发给Leader服务器。
当 Leader服务器出现崩溃退出或机器重启,亦或是集群中已经不存在过半的服务器与该Leader服务器保持正常通信时,那么在重新开始新一轮的原子广播事务操作之前,所有进程首先会使用崩溃恢复协议来使彼此达到一个一致的状态,于是整个ZAB流程就会从消息广播模式进入到崩溃恢复模式。
一个机器要成为新的 Leader,必须获得过半进程的支持,同时由于每个进程都有可能会崩溃,因此,在ZAB协议运行过程中,前后会出现多个 Leader,并且每个进程也有可能会多次成为 Leader。进入崩溃恢复模式后,只要集群中存在过半的服务器能够彼此进行正常通信,那么就可以产生一个新的 Leader并再次进入消息广播模式。举个例子来说,个由3台机器组成的ZAB服务,通常由1个Leader、2个 Follower服务器组成。某个时刻,假如其中一个 Follower服务器挂了,整个ZAB集群是不会中断服务的,这是因为 Leader服务器依然能够获得过半机器(包括Leader自己)的支持。
接下来我们就重点讲解一下ZAB协议的消息广播和崩溃恢复过程消息广播。
ZAB协议的消息广播过程使用的是一个原子广播协议,类似于一个二阶段提交过程。针对客户端的事务请求, Leader服务器会为其生成对应的事务 Proposal,并将其发送给集群中其佘所有的机器,然后再分别收集各自的选票,最后进行事务提交,下图就是ZAB协议消息广播流程的示意图。
在ZAB协议的二阶段提交过程中,移除了中断逻辑,所有的 Follower服务器要么正常反馈 Leader提出的事务 Proposal,要么就抛弃
Leader服务器。同时,ZAB协议将二阶段提交中的中断逻辑移除意味着我们可以在过半的 Follower服务器已经反馈Ack之后就开始提交事务 Proposal了,而不需要等待集群中所有的 Follower服务器都反馈响应。当然,在这种简化了的二阶段提交模型下,是无法处理 Leader服务器崩溃退出而带来的数据不一致问题的,因此在ZAB协议中添加了另一个模式,即采用崩溃恢复模式来解决这个问题。另外,整个消息广播协议是基于具有FIFO特性的TCP协议来进行网络通信的,因此能够很容易地保证消息广播过程中消息接收与发送的顺序性。
在整个消息广播过程中, Leader服务器会为每个事务请求生成对应的 Proposal来进行广播,并且在广播事务 Proposal之前, Leader服务器会首先为这个事务 Proposal分配一个全局单调递增的唯一ID,我们称之为事务ID(即ZXID)。由于ZAB协议需要保证每个消息严格的因果关系,因此必须将每一个事务 Proposal按照其ZXID的先后顺序来进行排序与处理。
具体的,在消息广播过程中, Leader服务器会为每一个 Follower服务器都各自分配一个单独的队列,然后将需要广播的事务Proposal依次放入这些队列中去,并且根据FIFO策略进行消息发送。每一个 Follower服务器在接收到这个事务 Proposal之后,都会首先将其以事务日志的形式写入到本地磁盘中去,并且在成功写入后反馈给 Leader服务器个Ack响应。当 Leader服务器接收到超过半数Follower的Ack响应后,就会广播一个Commit消息给所有的 Follower服务器以通知其进行事务提交,同时 Leader自身也会完成对事务的提交,而每一个 Follower服务器在接收到 Commit消息后,也会完成对事务的提交。
上面我们主要讲解了ZAB协议中的消息广播过程。ZAB协议的这个基于原子广播协议的消息广播过程,在正常情况下运行非常良好,但是一旦 Leader服务器出现崩溃,或者说由于网络原因导致 Leader服务器失去了与过半 Follower的联系,那么就会进入崩溃恢复模式。在ZAB协议中,为了保证程序的正确运行,整个恢复过程结束后需要选举出一个新的 Leader服务器。因此,ZAB协议需要一个高效且可靠的 Leader选举算法,从而确保能够快速地选举出新的 Leader。同时, Leader选举算法不仅仅需要让 Leader自己知道其自身已经被选举为 Leader,同时还需要让集群中的所有其他机器也能够快速地感知到选举产生的新的 Leader服务器。
根据上面的内容,我们了解到,ZAB协议规定了如果一个事务 Proposal在一台机器上被处理成功,那么应该在所有的机器上都被处理成功,哪怕机器出现故障崩溃。接下来我们看看在崩溃恢复过程中,可能会出现的两个数据不一致性的隐患及针对这些情况ZAB协议所需要保证的特性。
假设一个事务在 Leader服务器上被提交了,并且已经得到过半 Follower服务器的Ack反馈,但是在它将 Commit消息发送给所有 Follower机器之前, Leader服务器挂了,如图4-3所示。
图4-3中的消息C2就是一个典型的例子:在集群正常运行过程中的某一个时刻,Server1是 Leader服务器,其先后广播了消息P1、P2、C1、P3和C2,其中,当Leader服务器将消息C2(C2是 Commit Of Proposa2的缩写,即提交事务 Proposal2)发出后就立即崩溃退出了。针对这种情况,ZAB协议就需要确保事务 Proposal2最终能够在所有的服务器上都被提交成功,否则将出现不一致。
相反,如果在崩溃恢复过程中出现一个需要被丟弃的提案,那么在崩溃恢复结束后需要跳过该事务 Proposal,如图4-4所示。
在图4-4所示的集群中,假设初始的 Leader服务器 Server1在提出了一个事务Proposal3之后就崩溃退出了,从而导致集群中的其他服务器都没有收到这个事务Proposal。于是,当 Server l恢复过来再次加入到集群中的时候,ZAB协议需要确保丢弃 Proposal3这个事务。
结合上面提到的这两个崩溃恢复过程中需要处理的特殊情况,就决定了ZAB协议必须设计这样一个 Leader选举算法:能够确保提交已经被 Leader提交的事务 Proposal,同时丢弃已经被跳过的事务 Proposal。针对这个要求,如果让 Leader选举算法能够保证新选举出来的 Leader服务器拥有集群中所有机器最高编号(即ZXID最大)的事务 Proposal,那么就可以保证这个新选举出来的 Leader一定具有所有已经提交的提案。更为重要的是,如果让具有最高编号事务 Proposal的机器来成为 Leader,就可以省去 Leader服务器检查 Proposa的提交和丢弃工作的这一步操作了。
当leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的 Server都恢复到一个正确的状态。Zk的选举算法有两种:一种是基于basic paxos实现的,另外一种是基于fast paxos算法实现的。
系统默认的选举算法为fast paxos。
1 .选举线程由当前Server发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的Server;
2 .选举线程首先向所有Server发起一次询问(包括自己);
3 .选举线程收到回复后,验证是否是自己发起的询问(验证zxid是否一致),然后获取对方的id(myid),并存储到当前询问对象列表中,最后获取对方提议的leader相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中;
4. 收到所有Server回复以后,就计算出zxid最大的那个Server,并将这个Server相关信息设置成下一次要投票的Server;
5. 线程将当前zxid最大的Server设置为当前Server要推荐的Leader,如果此时获胜的Server获得n/2 + 1的Server票数, 设置当前推荐的leader为获胜的Server,将根据获胜的Server相关信息设置自己的状态,否则,继续这个过程,直到leader被选举出来。
通过流程分析我们可以得出:要使Leader获得多数Server的支持,则Server总数必须是奇数2n+1,且存活的Server的数目不得少于n+1.
在选举过程中,某Server首先向所有Server提议自己要成为leader,当其它Server收到提议以后,解决epoch和 zxid的冲突,并接受对方的提议,然后向对方发送接受提议完成的消息,重复这个流程,最后一定能选举出Leader。
完成 Leader选举之后,在正式开始工作(即接收客户端的事务请求,然后提出新的提案)之前, Leader服务器会首先确认事务日志中的所有 Proposal是否都已经被集群中过半的机器提交了,即是否完成数据同步。下面我们就来看看ZAB协议的数据同步过程。
所有正常运行的服务器,要么成为 Leader,要么成为 Follower并和 Leader保持同步。Leader服务器需要确保所有的 Follower服务器能够接收到每一条事务 Proposal,并且能够正确地将所有已经提交了的事务 Proposal应用到内存数据库中去。具体的, Leader服
务器会为每一个 Follower服务器都准备一个队列,并将那些没有被各 Follower服务器同步的事务以 Proposal消息的形式逐个发送给 Follower服务器,并在每一个 Proposal消息后面紧接着再发送一个 Commit消息,以表示该事务已经被提交。等到 Follower服务器将所有其尚未同步的事务 Proposal都从 Leader服务器上同步过来并成功应用到本地数据库中后, Leader服务器就会将该Follower服务器加入到真正的可用 Follower列表中,并开始之后的其他流程。
上面讲到的是正常情况下的数据同步逻辑,下面来看ZAB协议是如何处理那些需要被丢弃的事务 Proposal的。在ZAB协议的事务编号ZXID设计中,ZXID是一个64位的数字,其中低32位可以看作是一个简单的单调递增的计数器,针对客户端的每一个事务请求, Leader服务器在产生一个新的事务 Proposa的时候,都会对该计数器进行加1操作;而高32位则代表了 Leader周期 epoch的编号,每当选举产生一个新的 Leader服务器,就会从这个 Leader服务器上取出其本地日志中最大事务 Proposal的ZXID,并从该ZXID中解析出对应的 epoch值,然后再对其进行加1操作,之后就会以此编号作为新的 epoch,并将低32位置0来开始生成新的ZXID。ZAB协议中的这一通过 epoch编号来区分 Leader周期变化的策略,能够有效地避免不同的 Leader服务器错误地使用相同的ZXID编号提出不一样的事务 Proposal的异常情况,这对于识别在 Leader崩溃恢复前后生成的 Proposal非常有帮助,大大简化和提升了数据恢复流程。
基于这样的策略,当一个包含了上一个 Leader周期中尚未提交过的事务 Proposal的服务器启动时,其肯定无法成为 Leader,原因很简单,因为当前集群中一定包含一个Quorum集合,该集合中的机器一定包含了更高 epoch的事务 Proposal,因此这台机器的事务 Proposal肯定不是最高,也就无法成为 Leader了。当这台机器加入到集群中,以Follower角色连接上 Leader服务器之后, Leader服务器会根据自己服务器上最后被提交的 Proposal来和 Follower服务器的 Proposal进行比对,比对的结果当然是 Leader会要求 Follower进行一个回退操作—回退到一个确实已经被集群中过半机器提交的最新的事务 Proposal。举个例子来说,在图4-4中,当 Server1连接上 Leader后, Leader会要求 Server1去除P3。
ZAB协议并不是 Paxos算法的一个典型实现,在讲解ZAB和 Paxos之间的区别之前,我们首先来看下两者的联系。
在 Paxos算法中,一个新选举产生的主进程会进行两个阶段的工作。第一阶段被称为读阶段,在这个阶段中,这个新的主进程会通过和所有其他进程进行通信的方式来收集上个主进程提出的提案,并将它们提交。第二阶段被称为写阶段,在这个阶段,当前主进程开始提出它自己的提案。在 Paxos算法设计的基础上,ZAB协议额外添加了一个同步阶段。在同步阶段之前,ZAB协议也存在一个和 Paxos算法中的读阶段非常类似的过程,称为发现(Discovery)阶段。在同步阶段中,新的 Leader会确保存在过半的 Follower已经提交了之前 Leader周期中的所有事务 Proposal。这一同步阶段的引入,能够有效地保证 Leader在新的周期中提出事务 Proposal之前,所有的进程都已经完成了对之前所有事务 Proposal的提交。一旦完成同步阶段后,那么ZAB就会执行和 Paxos算法类似的写阶段。
总的来讲,ZAB协议和 Paxos算法的本质区别在于,两者的设计目标不太一样。ZAB协议主要用于构建一个高可用的分布式数据主备系统,例如 ZooKeeper,而 Paxos算法则是用于构建一个分布式的一致性状态机系统。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。