一、ZAB协议
ZAB协议全称就是ZooKeeper Atomic Broadcast protocol,是ZooKeeper用来实现一致性的算法,分成如下3个阶段:
- fast leader election:快速选举阶段
- recovery:恢复阶段
- Discovery;
- Synchrozation
- broadcasting:广播阶段
先了解一些基本概念
- electionEpoch:每执行一次leader选举,electionEpoch就会自增,用来标记leader选举的轮次
- peerEpoch:每次leader选举完成之后,都会选举出一个新的peerEpoch,用来标记事务请求所属的轮次
- zxid:事务请求的唯一标记,由leader服务器负责进行分配高32位是上述的peerEpoch,低32位是请求的计数,从0开始。
- lastprocessZxid:最后一次commit的事务请求的zxid
- LinkedList<Proposal> committedLog、long maxCommittedLog、long minCommittedLog:ZooKeeper会保存最近一段时间内执行的事务请求议案,个数限制默认为500个议案。committedLog就是用来保存议案的列表,maxCommittedLog表示最大议案的zxid,minCommittedLog表示committedLog中最小议案的zxid。
- ConcurrentMap<Long, Proposal> outstandingProposals:Leader拥有的属性,每当提出一个议案,都会将该议案存放至outstandingProposals,一旦议案被过半认同了,就要提交该议案,则从outstandingProposals中删除该议案
- ConcurrentLinkedQueue<Proposal> toBeApplied:Leader拥有的属性,每当准备提交一个议案,就会将该议案存放至该列表中,一旦议案应用到ZooKeeper的内存树中了,然后就可以将该议案从toBeApplied中删除。
- state:当前服务器的状态
- recvQueue:消息接收队列,用于存放那些从其他服务器接收到的消息。
- queueSendMap:消息发送队列,用于保存那些待发送的消息,按照SID进行分组。
- senderWorkerMap:发送器集合,每个SenderWorker消息发送器,都对应一台远程Zookeeper服务器,负责消息的发送,也按照SID进行分组。
- lastMessageSent:最近发送过的消息,为每个SID保留最近发送过的一个消息。
在ZAB协议中,服务的的状态state有四种:
- LOOKING:进入leader选举状态
- FOLLOWING:leader选举结束,进入follower状态
- LEADING:leader选举结束,进入leader状态
- OBSERVING:处于观察者状态
二、协议算法的具体描述
1、Broadcasting过程
- leader针对客户端的事务请求,创造出一个提案,zxid由leader决定,并将该提案的zxid,提案放到outstandingProposals Map中。
- leader向所有的follower发送该提案,如果过半的follower回复OK的话,则leader认为可以提交该议案,则将该议案从outstandingProposals中删除,然后存放到toBeApplied中
- leader对该议案进行提交,会向所有的follower发送提交该议案的命令,leader自己也开始执行提交过程,会将该请求的内容应用到ZooKeeper的内存树中,然后更新lastProcessedZxid为该请求的zxid,同时将该请求的议案存放到上述committedLog,同时更新maxCommittedLog和minCommittedLog
- leader回复客户端,并将提案中ToBeApplied中删除
2、fast leader election过程
选举过程关注两个要点:刚启动时进行leader选举和选举玩leader后,刚启动的server怎么感知到leader
投票过程有两个比较重要的数据:
- HashMap<Long, Vote> recvset:用于收集LOOKING、FOLLOWING、LEADING状态下的server的投票
-
HashMap<Long, Vote> outofelection:用于收集FOLLOWING、LEADING状态下的server的投票(说明leader选举已经完成)
具体的过程有:
1)服务器先自增electionEpoch,给自己投票:
从快照日志和事务日志中加载数据,得到本机器的内存树数据,以及lastProcessedZxid。投票内容为
- proposedLeader:server自身的myid值,初始为本机器的id
- proposedZxid:最大事务zxid,初始为本机器的lastProcessedZxid
- proposedEpoch:peerEpoch值,由上述的lastProcessedZxid的高32得到
然后向所有的服务器发送投票。
2)server接收到投票通知后,进行PK。
如果收到的通知中的electionEpoch比自己的大,则更新自己的electionEpoch为serverA的electionEpoch;如果、收到的通知中的electionEpoch比自己的小,则向serverA发送一个通知,将自己的投票以及electionEpoch发送给serverA,serverA收到后就会更新自己的electionEpoch。如果electionEpoch相同,PK的规则是proposedZxid,然后再是myId。
3)根据server的状态来判定leader
如果当前发来的投票的server的状态是LOOKING状态,则只需要判断本机器的投票是否在recvset中过半了,如果过半了则说明leader选举就算成功了,如果当前server的id等于上述过半投票的proposedLeader,则说明自己将成为了leader,否则自己将成为了follower。
如果当前发来的投票的server的状态是FOLLOWING、LEADING状态,则说明leader选举过程已经完成了,则发过来的投票就是leader的信息,这里就需要判断发过来的投票是否在recvset或者outofelection中过半了,同时还要检查leader是否给自己发送过投票信息,从投票信息中确认该leader是不是LEADING状态。
3、Recovery过程
一旦leader选举完成,就开始进入恢复阶段,就是follower要同步leader上的数据信息。
1)通信初始化
leader会创建一个ServerSocket,接收follower的连接,leader会为每一个连接会用一个LearnerHandler线程来进行服务;
2)重新为peerEpoch选举出一个新的peerEpoch
follower会向leader发送一个Leader.FOLLOWERINFO信息,包含自己的peerEpoch信息。leader的LearnerHandler会获取到上述peerEpoch信息,从中选出一个最大的peerEpoch,然后加1作为新的peerEpoch。然后leader的所有LearnerHandler会向各自的follower发送一个Leader.LEADERINFO信息,包含上述新的peerEpoch;follower会使用上述peerEpoch来更新自己的peerEpoch,同时将自己的lastProcessedZxid发给leader,leader的根据这个lastProcessedZxid和leader的lastProcessedZxid之间的差异进行同步。
3)已经处理的事务议案的同步
判断LearnerHandler中的lastProcessedZxid是否在minCommittedLog和maxCommittedLog之间
-
LearnerHandler中的lastProcessedZxid和leader的lastProcessedZxid一致,则说明已经保持同步了
-
如果lastProcessedZxid在minCommittedLog和maxCommittedLog之间,从lastProcessedZxid开始到maxCommittedLog结束的这部分议案,重新发送给该LearnerHandler对应的follower,同时发送对应议案的commit命令。上述可能存在一个问题:即lastProcessedZxid虽然在他们之间,但是并没有找到lastProcessedZxid对应的议案,即这个zxid是leader所没有的,此时的策略就是完全按照leader来同步,删除该follower这一部分的事务日志,然后重新发送这一部分的议案,并提交这些议案
-
如果lastProcessedZxid大于maxCommittedLog,则删除该follower大于部分的事务日志
-
如果lastProcessedZxid小于minCommittedLog,则直接采用快照的方式来恢复
4)未处理的事务议案的同步
LearnerHandler还会从leader的toBeApplied数据中将大于该LearnerHandler中的lastProcessedZxid的议案进行发送和提交(toBeApplied是已经被确认为提交的)
LearnerHandler还会从leader的outstandingProposals中大于该LearnerHandler中的lastProcessedZxid的议案进行发送,但是不提交(outstandingProposals是还没被被确认为提交的)
5) 将LearnerHandler加入到正式follower列表中
6)LearnerHandler发送Leader.NEWLEADER以及Leader.UPTODATE命令。leader开始进入心跳检测过程,不断向follower发送心跳命令,不断检是否有过半机器进行了心跳回复,如果没有过半,则执行关闭操作,开始进入leader选举状态;LearnerHandler向对应的follower发送Leader.UPTODATE,follower接收到之后,开始和leader进入Broadcast处理过程。
三、事务持久化和恢复过程
事务持久化分为:broadcasting持久化和leader shutdown过程的持久化。
- leader针对每次事务请求都会生成一个议案,然后向所有的follower发送该议案。follower收到提案后,将该议案记录到事务日志中,每当记满100000个(默认),则事务日志执行flush操作,同时开启一个新的文件来记录事务日志
同时会执行内存树的快照,snapshot.[lastProcessedZxid]作为文件名创建一个新文件,快照内容保存到该文件中
-
一旦leader过半的心跳检测失败,则执行shutdown方法,在该shutdown中会对事务日志进行flush操作
事务的恢复分为快照恢复和日志恢复。
- 事务快照的恢复:会在事务快照文件目录下找到最近的100个快照文件,并排序,最新的在前;对上述快照文件依次进行恢复和验证,一旦验证成功则退出,否则利用下一个快照文件进行恢复。恢复完成更新最新的lastProcessedZxid;
- 事务日志的恢复:从事务日志文件目录下找到zxid大于等于上述lastProcessedZxid的事务日志,然后对上述事务日志进行遍历,应用到ZooKeeper的内存树中,同时更新lastProcessedZxid,同时将上述事务日志存储到committedLog中,并更新maxCommittedLog、minCommittedLog