了解ZooKeepr内部原理

  • 本文不会讲解任何关于如何经过Zookeeper构建一个应用程序相关的知识,由于这种教程网上很是多,相信你们能够垂手可得的搭建.
  • 本文主要是介绍ZooKeeper内部是如何运行的,经过从高层次介绍其使用的协议,以及ZooKeeper所采用的在提供高性能的同时还具备容错能力的机制.

请求、事务和标识符

首先咱们先明确下,在ZooKeeper服务器中的几个基本概念。算法

请求:请求表示源自客户端发起的操做,事务一般包含节点中新的数据字段值和该节点新的版本号
事务:包含了对应请求处理而改变ZooKeeper状态所需执行的步骤
标识符:当群首收到了一个客户端改变ZooKeeper状态的请求,便会产生一个事务,就会为该事务分配一个标识符,即ZooKeeper会话ID(zxid),经过Zxid对事务进行标识,就能够按照群首所指定的顺序在各个服务器中按序执行.apache

zxid是一个long型(64)位整数,分为两部分:时间戳(epoch)部分和计数器(counter)部分.

群首选举

群首为集群中的服务器选择出来的一个服务器,并会被集群所承认.设置群首的目的是为了对客户端所发起的ZooKeeper状态变动请求进行排序,包括: createsetData、和 delete操做. 群首将每个请求转换为一个事务,将这些事务发送给追随者,确保集群按照群首肯定的顺序接受并处理事务.

这里为了了解管理权的原理,一个服务器必须被仲裁的法定数量的服务器所承认. 法定数量必须集群数量是指可以交错在一块儿,以免咱们常说的脑裂问题:即两个集合的服务器分别独立运行,造成了两个集群,这种状况将致使整个系统状态不一致.服务器

接下来咱们仔细探究下群首选举的过程:网络

每一个服务器在启动后进行LOOKING状态,开始选举一个新的群首或查找已经存在的群首,若是群首已经存在,其余服务器就会通知这个新启动的服务器,告知哪一个服务器是群首,与此同时,新的服务器会与群首创建链接,确保本身的状态与群首一致.性能

若是集群中全部的服务器均处LOOKING状态,这些服务器之间就会进行通讯来选举一个群首,经过信息交换对群首选举达成共识的选择.在本次选举中胜出的服务器将进入LEADING状态,而集群中其余服务器将进入FOLLOWING状态.学习

对于群首选举的通知协议其实很是简单:spa

当一个服务器进入LOOKING状态就会向集群中每一个服务器发送一个通知消息,该消息中包括该服务器的投票信息,投票中包含 服务器标识符(sid)最近执行的事务zxis信息 ,好比,一个服务器所发送的投票信息为(1,5) ,表示该服务器的sid为1, 最近执行的事务的zxid为5,(由于群首选举的目的,zxid只有一个数字,而在其余协议中,zxid则由时间戳和计数器组成).

当一个服务器收到一个投票信息,该服务器将会根据如下规则修改本身的投票信息:线程

  1. 将接收的voteId和voteZxid做为一个标识符,并获取当前本身投票中的zxid,用myZxid和mySid表示接收服务器本身的值.
  2. 若是(voteZxid < myZxid) 或者(voteZxid = myZxid 且 voteId < mySid),保留当前的投票信息
  3. 不然,修改本身的投票信息,将voteZxid 赋值给 myZxid,将voteId赋值给muSid;

简而言之,只有最新的服务器将赢得选举,由于其拥有最近一次的zxid.日志

在ZooKeeper中对应的实现选举的Java类为QuorumPeer,其中的run方法实现了服务器的主要工做循环.当进入LOOKING状态,将会执行looForLeader方法来进行群首的选举,该方法主要执行咱们上面所讨论的协议,该方法返回前,在该方法中会将服务器状态设置为LEADING 或 FOLLOWING状态,固然还可能为OBSERVING状态.

下面咱们经过一副来重温这个协议的执行过程:code

clipboard.png

群首选举时的长延迟

并非全部执行过程都如上图所示的那样,在下图中咱们,展现了另外一种状况的例子.
服务器s2作出了错误的判断,选举了另外一个服务器s1而不是服务器s3,虽然s3的zxid值更高,但在从服务器s3向s2传送消息时发生了网络故障致使长时间延迟,与此同时,服务器s2选择了服务器s1做为群首,最终,服务器s3和服务器S1组成了仲裁数量(quorum),并将忽略服务器s2.

clipboard.png

从这里能够看出,若是服务器s2在进行群首选举时多等待一会,它就能作出正确的判断.

若是想实现一个新的群首选举算法,咱们须要实现一个quorum包中的Election接口.
ZooKeeper包中已经有了让咱们本身选择群首选举实现的类,具体能够查看org.apache.zookeeper.server.quorum.QuorumPeer#createElectionAlgorithm方法;

Zab: ZooKeeper 状态更新的广播协议

Zab: ZooKeeper原子广播协议(ZooKeeper Atomic Broadcast protocol),用于服务器提交确认一个更新事务的提交.

下面举个例子,假设咱们如今有一个活动的群首服务器,并拥有仲裁数量的追随者支持该群首的管理权,经过该协议提交一个事务,相似于一个两阶段提交:

  1. 群首向全部追随者发送一个PROPOSAL消息p.
  2. 当一个追随者收到消息p后,会响应群首一个ACK消息,通知群首其已接受该提案(proposal)
  3. 当收到仲裁数量的服务器发送确认消息后(该仲裁包括群首本身),群首就会发送消息通知追随者进行提交(COMMIT)操做.

下图说明了这一过程的具体步骤顺序,咱们假设群首经过隐式方式给本身发送消息.

提交提案的常规消息模式

在应答消息以前,追随者还须要执行一些检查操做.追随者将会检查所发送的提案消息是否属于其所追随的群首,并确认群首所广播的提案消息和提交事务消失的顺序正确;

Zab保障了如下几个重要属性:

  • 若是群首按顺序广播了事务T和事务T',那么每一个服务器在提交T'事务前保证事务T已经提交完成;
  • 若是某个服务器按照事务T、事务T'的顺序提交事务,全部其余服务器也必然会在提交事务T'前提交事务T;

第一个属性保证事务在服务器之间的传送顺序一致,而第二个竖向地保证服务器不会跳过任何事务.假设事务为状态变动操做,每一个状态变动操做又依赖前一个状态变动操做的结果,若是跳过事务就会致使结果不一致性,而两阶段提交保证了事务的顺序.

深刻代码:

大部分Zab的代码存在于Leader、LearnerHandler和Follower。Leader和LearderHandler的实例由群首服务器执行,而Follower的实例由追随者执行。Leader.leadFollower.followLeader 是两个重要方法,他们在服务器在QuorumPeer 中从 LOOKING转换到LEADING或者FOLLOWING时获得调用.

观察者

观察者和追随者之间有一些共同点,具体来讲,它们提交来自群首的提议. 不一样于追随者的是,观察者不参与选举过程,它们仅仅学习经由INFORM消息提交的提议. 因为群首将状态变化发送给追随者和观察者,这两种服务器也都被称为学习者.

服务器的构成

群首,追随者基本上都是服务器.咱们在实现服务器时使用的主要抽象概念是请求处理器. 请求处理器是对处理流水线上不一样阶段的抽象. 每个服务器实现了一个请求处理器的序列.咱们能够把一个处理器想象成添加到请求处理的一个元素. 一条请求通过服务器流水线上全部处理器的处理后被称为获得彻底处理.

注意: 请求处理器
ZooKeeper代码里有一个交RequestProcessor的接口.这个接口的主要方法是processRequest,它接受一个Request的参数.在一条请求处理器的流水线上,对相邻处理器的请求的处理一般经过队列实现解耦合.当一个处理器有一条请求须要下一个处理器进行处理时,它将这条请求加入队列.而后,它将处于等待状态直到下一个处理器处理完此消息;

独立服务器

ZooKeeper中最简单的流水线式独立服务器(ZooKeeperServer类).下图描述了此类服务器的流水线:

一个独立服务器的流水线

PrepRequestProcessor接口客户端的请求并执行这个请求,处理结果则是生成一个事务. 事务是执行一个操做的结果, 该操做会反映到ZooKeeper的数据树上. 事务信息将会以头部记录和事务记录的方式添加到Request对象中. 同时还要注意,只有改变ZooKeeper状态的操做才会产生事务,对于读操做并不会产生任何事务. 所以,对于读请求的Request对象中,事务成员属性的引用值为null.

下一个请求处理器为SynRequestProcessor. 它负责将事务持久化到磁盘上. 实际上就是将事务数据按照顺序追加到事务日志中, 并生成快照数据.

在下一个处理器也是最后一个为FinalRequestProcessor.若是Request对象包含事务数据,该处理器将会接受对ZooKeeper数据树的修改,不然该处理器会从数据树中读取数据并返回给客户端.

群首服务器

当切换到仲裁模式时,服务器的流水线则有一些变化,下图展现了群首操做流水线(类LeaderZooKeeper)

群首服务器流水线

第一个处理器一样是PrepRequestProcessor,而以后的处理器则为ProposalRequestProcessor.该处理器会准备一个提议,并将该提议发送给跟随者. ProposalRequestProcessor将会把全部请求都转发给CommitRequestProcessor,并且对于写操做请求,还会将请求转发给SyncRequestProcessor 处理器.

SyncRequestProcessor 处理器所执行的操做与独立服务器同样,即持久化操做. 执行完以后会触发AckRequestProcessor处理器,它是一个简单请求处理器,它仅仅生成确认消息并返回给本身. 此处即是上文中提到的,在仲裁模式下,群首须要收到每一个服务器的确认消息,也包括本身,而AckRequestProcessor 处理器就负责这个;

CommitRequestProcessor 会将收到足够多的确认消息的提议进行提交. 实际上,确认消息是由Leader类处理的(Leader.processAck()方法),这个方法会将提交的请求加入到CommitRequestProcessor类中的一个队列中.这个队列会由请求处理器线程进行处理.

FinalRequestProcessor处理器,做用与独立服务器同样,不过在它以前还有一个简单的请求处理器,这个处理器会从提议列表中删除那些待接受的提议,这个处理器的名字叫ToBeAppliedRequestProcessor,待接受请求列表包括那些已经被仲裁法定人数所确认的请求,并等待执行. 群首使用这个列表与追随者之间进行同步,并将收到确认消息的请求加入到这个列表中. 以后 ToBeAppliedRequestProcessor 处理器就会在 FinalRequestProcessor处理器执行后删除这个列表中的元素;

注意: 只有更新请求才会加入到待接受请求列表中,而后由 ToBeAppliedRequestProcessor处理器从该列表删除. ToBeAppliedRequestProcessor处理器并不会对读取请求进行任何额外的处理操做,而是由 FinalRequestProcessor处理器进行操做.

追随者服务器

最后来看一下追随者(FollowerRequestProcessor类),下图展现了一个追随者服务器中会用到的请求处理器:

追随者服务器流水线

首先从FollowerRequestProcessor处理器开始,该处理器接收并处理客户端请求. FollowerRequestProcessor 处理器以后转发请求给CommitRequestProcessor,同时也会转发写请求到群首服务器. CommitRequestProcessor 会直接转发读请求到FinalRequestProcessor 处理器, 并且对于写请求, CommitRequestProcessor在转发给 FinalRequestProcessor处理器以前会等待提交事务.

当群首接收到一个新的写请求操做时,直接地或经过其余追随者服务器生成一个提议(proposal),以后转发到追随者服务器. 当收到一个提议,追随者会发送这个提议道SyncRequestProcessor 处理器,SendRequestProcessor会向群首发送确认消息. 当群首收到足够确认消息来提交这个提议时,群首就会发送提交事务消息给追随者(同时也会发哦少年宫INFORM消息给观察者服务器). 当接收到提交事务消息时,追随者就经过CommitRequestProcessor 处理器进行处理.

为了保证执行的顺序, CommitRequestProcessor 处理器会在收到一个写请求处理器时暂停后续的请求处理. 这就意味着, 在一个写请求以后接收到的任何读取请求都将被阻塞,直到读取请求转给 CommitRequestProcessor处理器. 经过等待的方式, 请求能够被保证按照接收的顺序来执行.

小结:

本文讨论了ZooKeeper核心机制问题. 群首竞选机制是可用性的关键因素,没有这个机制,ZooKeeper套件将没法保持可靠性. Zookeeper还须要Zab协议来传播状态的更新. 紧接着 阐述了多种服务器类型:包括独立服务器,群首服务器,追随者服务器. 这些服务器之间因运起色制和执行协议的不一样而不一样. 在不一样的部署场景中,各个服务器能够发挥不一样的做用, 好比 增长观察者服务器能够提供更高的读吞吐量,并且还不会影响写吞吐量. 不过增长观察者服务器并不会增长整个系统的高可用性.
相关文章
相关标签/搜索