1.1.1 Zookeeper的保证node
l 顺序性,client的updates请求都会根据它发出的顺序被顺序的处理;算法
l 原子性, 一个update操做要么成功要么失败,没有其余可能的结果;数据库
l 一致的镜像,client不论链接到哪一个server,展现给它都是同一个视图;缓存
l 可靠性,一旦一个update被应用就被持久化了,除非另外一个update请求更新了当前值服务器
l 实时性,对于每一个client它的系统视图都是最新的网络
1.1.2 Zookeeper server角色session
领导者(Leader) : 领导者不接受client的请求,负责进行投票的发起和决议,最终更新状态。分布式
跟随者(Follower): Follower用于接收客户请求并返回客户结果。参与Leader发起的投票。性能
观察者(observer): Oberserver能够接收客户端链接,将写请求转发给leader节点。可是Observer不参加投票过程,只是同步leader的状态。Observer为系统扩展提供了一种方法。学习
学习者 ( Learner ) : 和leader进行状态同步的server统称Learner,上述Follower和Observer都是Learner。
1.1.3 Zookeeper集群
一般Zookeeper由2n+1台servers组成,每一个server都知道彼此的存在。每一个server都维护的内存状态镜像以及持久化存储的事务日志和快照。对于2n+1台server,只要有n+1台(大多数)server可用,整个系统保持可用。
系统启动时,集群中的server会选举出一台server为Leader,其它的就做为follower(这里先不考虑observer角色)。接着由follower来服务client的请求,对于不改变系统一致性状态的读操做,由follower的本地内存数据库直接给client返回结果;对于会改变系统状态的更新操做,则交由Leader进行提议投票,超过半数经过后返回结果给client。
二.Zookeeper server工做原理
Zookeeper的核心是原子广播,这个机制保证了各个server之间的同步。实现这个机制的协议叫作Zab协议。Zab协议有两种模式,它们分别是恢复模式和广播模式。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数server的完成了和leader的状态同步之后,恢复模式就结束了。状态同步保证了leader和server具备相同的系统状态。
一旦leader已经和多数的follower进行了状态同步后,他就能够开始广播消息了,即进入广播状态。这时候当一个server加入zookeeper服务中,它会在恢复模式下启动,发现leader,并和leader进行状态同步。待到同步结束,它也参与消息广播。Zookeeper服务一直维持在Broadcast状态,直到leader崩溃了或者leader失去了大部分的followers支持。
Broadcast模式极其相似于分布式事务中的2pc(two-phrase commit 两阶段提交):即leader提起一个决议,由followers进行投票,leader对投票结果进行计算决定是否经过该决议,若是经过执行该决议(事务),不然什么也不作。
广播模式须要保证proposal被按顺序处理,所以zk采用了递增的事务id号(zxid)来保证。全部的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64为的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch。低32位是个递增计数。
当leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式,恢复模式须要从新选举出一个新的leader,让全部的server都恢复到一个正确的状态。
首先看一下选举的过程,zk的实现中用了基于paxos算法(主要是fastpaxos)的实现。具体以下:
1.每一个Server启动之后都询问其它的Server它要投票给谁。
2.对于其余server的询问,server每次根据本身的状态都回复本身推荐的leader的id和上一次处理事务的zxid(系统启动时每一个server都会推荐本身)
3.收到全部Server回复之后,就计算出zxid最大的哪一个Server,并将这个Server相关信息设置成下一次要投票的Server。
4.计算这过程当中得到票数最多的的sever为获胜者,若是获胜者的票数超过半数,则改server被选为leader。不然,继续这个过程,直到leader被选举出来。
此外恢复模式下,若是是从新刚从崩溃状态恢复的或者刚启动的的server还会从磁盘快照中恢复数据和会话信息。(zk会记录事务日志并按期进行快照,方便在恢复时进行状态恢复)
选完leader之后,zk就进入状态同步过程。
1.leader就会开始等待server链接
2.Follower链接leader,将最大的zxid发送给leader
3.Leader根据follower的zxid肯定同步点
4.完成同步后通知follower 已经成为uptodate状态
5.Follower收到uptodate消息后,又能够从新接受client的请求进行服务了。
三. ZookeeperServer工做流程
3.1.1 主线程的工做:
1. 刚开始时各个Server处于一个平等的状态peer
2. 主线程加载配置后启动。
3. 主线程启动QuorumPeer线程,该线程负责管理多数协议(Quorum),并根据表决结果进行角色的状态转换。
4. 而后主线程等待QuorumPeer线程。
3.1.2 QuorumPeer线程
1. 首先会从磁盘恢复zkdatabase(内存数据库),并进行快照回复。
2. 而后启动server的通讯线程,准备接收client的请求。
3. 紧接着该线程进行选举leader准备,选择选举算法,启动response线程(根据自身状态)向其余server回复推荐的leaer。
4. 刚开始的时候server都处于looking状态,进行选举根据选举结果设置本身的状态和角色。
3.1.3 quorumPeer有几种状态
1. Looking: 寻找状态,这个状态不知道谁是leader,会发起leader选举
2. Observing: 观察状态,这时候observer会观察leader是否有改变,而后同步leader的状态
3. Following: 跟随状态,接收leader的proposal ,进行投票。并和leader进行状态同步
4. Leading: 领导状态,对Follower的投票进行决议,将状态和follower进行同步
当一个Server发现选举的结果本身是Leader把本身的状态改为Leading,若是Server推荐了其余人为Server它将本身的状态改为Following。作Leader的server若是发现拥有的follower少于半数时,它从新进入looking状态,从新进行leader选举过程。(Observing状态是根据配置设置的)。
3.2 Leader的工做流程:
3.2.1 Leader主线程:
1.首先leader开始恢复数据和清除session
启动zk实例,创建请求处理链(Leader的请求处理链):PrepRequestProcessor->ProposalRequestProcessor->CommitProcessor->Leader.ToBeAppliedRequestProcessor ->FinalRequestProcessor
2.获得一个新的epoch,标识一个新的leader , 并得到最大zxid(方便进行数据同步)
3.创建一个学习者接受线程(来接受新的followers的链接,follower链接后肯定followers的zxvid号,来肯定是须要对follower进行什么同步措施,好比是差别同步(diff),仍是截断(truncate)同步,仍是快照同步)
4. 向follower创建一个握手过程leader->follower NEWLEADER消息,并等待直到多数server发送了ack
5. Leader不断的查看已经同步了的follower数量,若是同步数量少于半数,则回到looking状态从新进行leaderElection过程,不然继续step5.
3.2.2 LearnerCnxAcceptor线程
1.该线程监听Learner的链接
2.接受Learner请求,并为每一个Learner建立一个LearnerHandler来服务
3.2.3 LearnerHandler线程的服务流程
1.检查server来的第一个包是否为follower.info或者observer.info,若是不是则没法创建握手。
2. 获得Learner的zxvid,对比自身的zxvid,肯定同步点
3.和Learner创建第二次握手,向Learner发送NEWLEADER消息
4.与server进行数据同步。
5.同步结束,知会server同步已经ok,能够接收client的请求。
6. 不断读取follower消息判断消息类型
i. 若是是LEADER.ACK,记录follower的ack消息,超过半数ack,将proposal提交(Commit)
ii. 若是是LEADER.PING,则维持session(延长session失效时间)
iii. 若是是LEADER.REQEST,则将request放入请求链进行处理–Leader写请求发起proposal,而后根据follower回复的结果来肯定是否commit的。最后由FinallRequestProcessor来实际进行持久化,并回复信息给相应的response给server
3.3 Follower的工做流程:
1.启动zk实例,创建请求处理链:FollowerRequestProcessor->CommitProcessor->FinalProcessor
2.follower首先会链接leader,并将zxid和id发给leader
3.接收NEWLEADER消息,完成握手过程。
4.同leader进行状态同步
5.完成同步后,follower能够接收client的链接
5.接收到client的请求,根据请求类型
l 对于写操做, FollowerRequestProcessor会将该操做做为LEADER.REQEST发给LEADER由LEADER发起投票。
l 对于读操做,则经过请求处理链的最后一环FinalProcessor将结果返回给客户端
对于observer的流程再也不赘述,observer流程和Follower的惟一不一样的地方就是observer不会参加leader发起的投票。
三.关于Zookeeper的扩展
为了提升吞吐量一般咱们只要增长服务器到Zookeeper集群中。可是当服务器增长到必定程度,会致使投票的压力增大从而使得吞吐量下降。所以咱们引出了一个角色:Observer。
Observers 的需求源于 ZooKeeper follower服务器在上述工做流程中实际扮演了两个角色。它们从客户端接受链接与操做请求,以后对操做结果进行投票。这两个职能在 ZooKeeper集群扩展的时候彼此制约。若是咱们但愿增长 ZooKeeper 集群服务的客户数量(咱们常常考虑到有上万个客户端的状况),那么咱们必须增长服务器的数量,来支持这么多的客户端。然而,从一致性协议的描述能够看到,增长服务器的数量增长了对协议的投票部分的压力。领导节点必须等待集群中过半数的服务器响应投票。因而,节点的增长使得部分计算机运行较慢,从而拖慢整个投票过程的可能性也随之提升,投票操做的会随之降低。这正是咱们在实际操做中看到的问题——随着 ZooKeeper 集群变大,投票操做的吞吐量会降低。
因此须要增长客户节点数量的指望和咱们但愿保持较好吞吐性能的指望间进行权衡。要打破这一耦合关系,引入了不参与投票的服务器,称为 Observers。 Observers 能够接受客户端的链接,将写请求转发给领导节点。可是,领导节点不会要求 Observers 参加投票。相反,Observers 不参与投票过程,仅仅和其余服务节点一块儿获得投票结果。
这个简单的扩展给 ZooKeeper 的可伸缩性带来了全新的镜像。咱们如今能够加入不少 Observers 节点,而无须担忧严重影响写吞吐量。规模伸缩并不是无懈可击——协议中的一歩(通知阶段)仍然与服务器的数量呈线性关系。可是,这里的穿行开销很是低。所以能够认为在通知服务器阶段的开销没法成为主要瓶颈。
上图显示了一个简单评测的结果。纵轴是从一个单一的客户端发出的每秒钟同步写操做的数量。横轴是 ZooKeeper 集群的尺寸。蓝色的是每一个服务器都是 voting 服务器的状况,而绿色的则只有三个是 voting 服务器,其它都是 Observers。图中看到,扩充 Observers,写性能几乎能够保持不变,但若是同时扩展 voting 节点的数量的话,性能会明显降低。显然 Observers 是有效的。所以Observer能够用于提升Zookeeper的伸缩性。
此外Observer还能够成为特定场景下,广域网部署的一种方案。缘由有三点:1.为了得到更好的读性能,须要让客户端足够近,但若是将投票服务器分布在两个数据中心,投票的延迟太大会大幅下降吞吐,是不可取的。所以但愿可以不影响投票过程,将投票服务器放在同一个IDC进行部署,Observer能够跨IDC部署。2. 投票过程当中,Observer和leader之间的消息、要远小于投票服务器和server的消息,这样远程部署对带宽要求就较小。3.因为Observers即便失效也不会影响到投票集群,这样若是数据中心间链路发生故障,不会影响到服务自己的可用性。这种故障的发生几率要远高于一个数据中心中机架间的链接的故障几率,因此不依赖于这种链路是个优势。
Znodes
每一个节点在zookeeper中用znode表示。znodes 包含数据变动和acl变动的版本号。znode一样包含时间戳。版本号和时间戳用来帮助zookeeper验证缓存或者协调更新。每次znode数据发生变化都会使版本号增长。例如,每次client接受数据时都会接收到数据的版本号。当client更新或者删除数据时必须给znode提供数据的版本号。若是提供的版本号与实际的版本号不匹配,更新操做会失败。
znode是程序访问的主要实体类。包含以下特性:
Watches
clients 能够为znode设置watch。znode发生改变将会触发watch。当一个watch触发,zookeeper会向client发送通知。
Data Access
在namespace中存储在每一个znode上的数据发生的读写操做都是原子性的。读一个znode上的所有数据或者替换掉所有数据都是原子性的。每一个znode都有一个Access Contron List(ACL)用来约束哪些人能够执行相应操做。
Zookeeper不是用来作数据库或者存贮大对象的。相反,它只负责协调数据。数据能够来自配置表单、结构化信息等等。这些数据的有一个共同的特色那就是都很小:以Kb为测量单位。Zookeeper的client和server的实现类都会验证znode存储的数据是否小于1M,可是数据应该比平均值小的多。操做大数据将会触发一些消耗时间的额外操做而且影响潜在的操做,由于须要额外的时间在网络和存储介质上转移数据。若是有大数据须要存储,一般的办法是把这些数据存储在专门的大型文件系统上,例如NFS或者HDFS,而后把存储介质的位置存在zookeeper上。
Ephemeral Nodes
zookeeper有一种znode是ephemeral nodes。这些znode只在session存在期间有效。当session结束的时候这些ephemeral nodes被删除。因此ephemeral znodes不能有子节点。
Sequence Nodes -- Unique Naming
当建立一个znode时候,你也能够要求zookeeper在path的结尾单调递增。计数器对每个znode来讲都是惟一的。计数器使用%010d格式化--例如<path>0000000001。注意:计数器使用一个singed int(4bytes)来存储下一个序列值。因此计数器达到2147483647 后会溢出。
zookeeper的每一个节点能够有以下三种角色:
1.leader和follower
ZooKeeper须要在全部的服务(能够理解为服务器)中选举出一个Leader,而后让这个Leader来负责管理集群。此时,集群中的其它服务器则成为此Leader的Follower。而且,当Leader故障的时候,须要ZooKeeper可以快速地在Follower中选举出下一个Leader。这就是ZooKeeper的Leader机制,下面咱们将简单介绍在ZooKeeper中,Leader选举(Leader Election)是如何实现的。
此操做实现的核心思想是:首先建立一个EPHEMERAL目录节点,例如“/election”。而后。每个ZooKeeper服务器在此目录下建立一个SEQUENCE|EPHEMERAL类型的节点,例如“/election/n_”。在SEQUENCE标志下,ZooKeeper将自动地为每个ZooKeeper服务器分配一个比前一个分配的序号要大的序号。此时建立节点的ZooKeeper服务器中拥有最小序号编号的服务器将成为Leader。
在实际的操做中,还须要保障:当Leader服务器发生故障的时候,系统可以快速地选出下一个ZooKeeper服务器做为Leader。一个简单的解决方案是,让全部的follower监视leader所对应的节点。当Leader发生故障时,Leader所对应的临时节点将会自动地被删除,此操做将会触发全部监视Leader的服务器的watch。这样这些服务器将会收到Leader故障的消息,并进而进行下一次的Leader选举操做。可是,这种操做将会致使“从众效应”的发生,尤为当集群中服务器众多而且带宽延迟比较大的时候,此种状况更为明显。
在Zookeeper中,为了不从众效应的发生,它是这样来实现的:每个follower对follower集群中对应的比本身节点序号小一号的节点(也就是全部序号比本身小的节点中的序号最大的节点)设置一个watch。只有当follower所设置的watch被触发的时候,它才进行Leader选举操做,通常状况下它将成为集群中的下一个Leader。很明显,此Leader选举操做的速度是很快的。由于,每一次Leader选举几乎只涉及单个follower的操做。
2.Observer
observer的行为在大多数状况下与follower彻底一致, 可是他们不参加选举和投票, 而仅仅接受(observing)选举和投票的结果.