第一章 Zookeeper server
1.1 Zookeeper基本原理
1.1.1 Zookeeper的保证
l 顺序性,client的updates请求都会根据它发出的顺序被顺序的处理;
l 原子性, 一个update操作要么成功要么失败,没有其他可能的结果;
l 一致的镜像,client不论连接到哪个server,展示给它都是同一个视图;
l 可靠性,一旦一个update被应用就被持久化了,除非另一个update请求更新了当前值
l 实时性,对于每个client它的系统视图都是最新的
1.1.2 Zookeeper server角色
领导者(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即使失效也不会影响到投票集群,这样如果数据中心间链路发生故障,不会影响到服务本身的可用性。这种故障的发生概率要远高于一个数据中心中机架间的连接的故障概率,所以不依赖于这种链路是个优点。