[引导,选举过程]
1. 选举开始,当前server整理出自己的serverId,zxid(事务id,来自ZKDatabase),peerEpoch(此值来自文件,每一轮选举成功后都会导致epoch值改动,此值会被存储在文件中,此值用来标记leader,zxid由"epoch + 自增id"组成,zxid的前32位即为epoch),然后向所有的PARTICIPANT类型(非observer类型的其他server,包括自己,参见LearnerType)的server发送自己的"投票".
投票信息包括:
a) 提议的serverId
b) 自己的serverId:标记nofification的来源
c) 提议的zxid
d) 进行中的epoch:用来标记选举的进度(logicalclock,有效的投票需要在同一个进度上)
e) 自己的server状态:接收方会根据发送者的server状态,决定ACK消息的内容.
f) 提议的epoch:每次投票,server都会把自己的epoch发出.即peerEpoch.
2. 自己的提议发出后,开始轮询server状态,直到非LOOKING状态才结束选举.
3. 亟待发送的提议或者ACK均会保存在sendqueue中,尚未处理的收到的提议/ACK保存在recvqueue中.FastLeaderElection将会启动2个守护(daemon)子线程:
在此之前需要提示一下,FasterLeaderElection在初始化时,会连带初始化一个QuorumCnxManager,此对象主要负责和所有的peer(非observer机器)建立tcp链接(发送投票),同时侦听选举的端口(接受其他server的投票);并把响应交付给WorkerReceiver和WorkerSender处理.在FastLeaderElection.lookForLeader()中开启操作.如下2个线程都持有QuorumCnxManager的引用.
a) WorkerReceiver线程:轮询manager接受到其他server的投票/ACK信息,并做响应的处理.ACK信息我们可以看做是其他server对投票的响应,事实上和投票消息没什么区别.
<1>从消息中获取投票者所提议的leader(serverId),zxid,electionEpoch,peerEpoch,serverState;并封装成Notification
<2>如果此时server仍LOOKING,那么将<1>notification添加到recvqueue中.此时如果投票者的状态也是LOOKING且electionEpoch < 自己的logicalclock,将自己的提议发送给投票者(添加到sendqueue中).
<3>如果此时server状态非LOOKING,但是投票者的状态为LOOKING,那么也将自己的提议(目前的leader信息)发送给投票者(添加到sendqueue中).
上述过程,也就是各个server直接不断互相通信,并达成一致协议的过程.
b) WorkerSender线程:此线程所做的事情,就非常的简单,从sendqueue中获取notification,然后交付给manager通过socket发送给响应的server.
4. 核心逻辑部分:
2)之后,从recvqueue取出一个notification.
<1>如果此消息的electionEpoch > logicalclock,即其他server开始了更高的选举次数,此时清除自己所持的投票结果(意思就是,接受新一轮的投票),将electionEpoch赋值给本地的logicalclock,开始新一轮投票.
此时检测消息的提议是否可以接受,检测方式(totalOrderPredicate):
(newEpoch > curEpoch) || ((newEpoch == curEpoch) && (newZxid > curZxid)) || ((newZxid == curZxid) && (newId > curId))
意思为:提议的peerEpoch和当前server的peerEpoch比较大小,如果提议的peerEpoch更大,那么接受此提议.如果相等,那么就检测各自的zxid,如果提议者的zxid更大,接受此提议..如果zxid也相等,那么就比较他们各自的serverId,如果提议者的serverId更大,接受提议.否则,此提议被抛弃.
所有接受提议的地方,均会updateProposal,即将提议的zid,electionEpoch,zxid,peerEpoch保存起来,作为以后自己投票提议的对象.
将自己最新的提议对象,发送给所有的peer.
此分支流结束
<2>如果此消息的electionEpoch < logicalclock,直接抛弃,重新开始2)
<3>如果此消息electionEpoch == logicalclock,检测提议是否可以接受,如果是,updateProposal,然后发送自己最新的提议.
<4>将提议者的信息封装成vote,存储在自己本地的投票结果中--recvset.
<5>此时提议者的消息已经处理完毕,来时校验投票结果.将截至至此,所收集的最终提议信息(最高的electionEpoch,zxid,serverId,peerEpoch)和投票结果集合(recvset)比较,检测是否有足够的vote和最终提议一致,如果是(2n+1),将可以判定投票结果已定,可以终止投票了.(termPredicate方法)
<6>不过事情似乎还有些变化,在判定成功之后,将会检测recvqueue中剩余的提议,剔除"不可接受的提议(比较低的提议)",如果发现仍然有可以接受的提议(且此提议是更高的),那么重复2)(重新投票,可能是因为有其他机器加入等造成的,且此机器具有更新的数据状态).否则就认为目前的投票结果可以接受.
投票结果确定之后,当前server就会校验最后确定的serverId是否为自己,如果是就将自己的状态改为LEADING,否则改为FOLLOWING;然后清除recvqueue结果,最后返回投票结果给QuorumPeer.
QuorumPeer根据结果和当前server的状态做相关操作.
5. 如果server发起投票,在投票过程中,收到其他server的回应中server状态为FOLLOWING或者LEADING:
<1>如果logicalclock == electionEpoch,此时会把回应的提议加入到自己的投票记录集合中.然后做2个校验:
---->从recvset中检测是否可以结束此轮投票(termPredicate方法,检测多数派是否出现).
---->从outofelction中检测Leader状态,outofelction保存了"非投票时机"(其他server已经确定了Leader之后,或者server正常无需投票)接受到的其他server的提议,检查leader也就是从outoofelection集合中查找自己是否收到了状态为LEADING的server提议.
如果上述2个校验通过,那么当前server也可以简单的认为Leader已经被选出(或者此前Leader状态正常),当前server结束投票,改变状态.
<2>如果logicalclock != electionEpoch,将此提议加入到outofelection,然后做<1>一样的检测,如果检测通过,更改logicalclock = electionEpoch,然后结束投票,更改自己的状态.
如果不通过,继续2)循环recvqueue.
6. 当leader选举成功,FastLeaderElection的2个子线程(WorkerSender和WorkerReceiver)以及QuromCnxnManager的2个子线程(SenderWorder和RecvWorker)并不退出,仍然会不断接受和发送消息,以便处理那些“苏醒”的Follower加入集群的情况。当一个Follower“苏醒”,会向其他peer发送选举消息,当其他peer接受到消息后,会检测自己的状态,如果自己的状态不是LOOKING,将会向此Follower回复自己的logicalclock/此时Leader的zxid,serverId以及自己的serverState。让此Follower的很快进入Following状态。
7. zookeeper的每个peer都互相建立tcp链接,zookeeper采取一个很奇妙的办法,每个peer主动向比自己的serverId小的server建立连击,并只accept比自己serverId大的peer的链接申请。并且这种“有向”的链接维护关系,会在peer的加入/离群不断调整。
Leader和Follower之间数据传输类型,数据packet中都会包含一个特定的类型用来告知对方如何处理数据,这些类型为常量值,在Leader类中.
1) Leader.DIFF 用于在leader和follower之间进行数据同步时,”开始同步”的信号
2) Leader.TRUNC 用于在leader和follower之间进行数据同步时,”要求follower进行”移除非一致性数据”的信号,一般在传递TRUNC类型的packet时,都会向follower交付需要删除数据的起始ZXID.
3) Leader.SNAP 用于在leader和follower之间进行数据同步时,表示follower已经”落后”太远需要全量dump数据,此时follower所做的事情就是清空自己的所有数据,全权接受leader的snap内容.
4) Leader.OBSERVERINFO 表示leader和”Follower”通讯的连接中的两端,有一端为”观察者(Observer)”,用于身份确认.
5) Leader.NEWLEADER 表示当前消息为”新Leader”标记,标明”旧leader”数据已经确认完毕,接下来Leader和Follower需要对在新leader下开始write/read操作.此信号通常在选举结束并leader身份确认之后,leader发送给Follower,follower根据此消息来确认自己zxid是否正确,并触发snapshot操作.(snapshot即为把当前内存数据,db数据的最新状态重新序列化到文件)
6) Leader.FOLLOWERINFO 同4),身份确认,不过对于follower,在发送此类型给Leader时尚会交付自己持有的最大ZXID,leader可以决定是否需要同步数据.(选举结束后)
7) Leader.UPTODATE “标记”信息,在leader向follower发送同步数据时,如果数据已经发送完毕,则补充一个UPTODATE,”告知”follower已经持有了最新数据,即可为client 服务.
8) Leader.LEADERINFO 当Follower和Leader建立连接后(Leader主动建立),leader会首先向Follower交付一个LEADERINFO信息(包括此时leader的newEpoch,zxid).此信息,是Leader和Follower开始对话的”开始”.
9) Leader.ACKEPOCH Follower收到Leader的EPOCH之后,向Leader发送的确认.
10) Leader.REQUEST 由Follower向Leader发送的数据变更请求(转发),接下来将会被Leader接受和解析.
11) Leader.PROPOSAL 由leader向Follower/Observer发送的数据变更提议(基于2阶段提交),此后Follower或者Observer将有机会接收到packet,并等到COMMIT信息.
12) Leader.ACK 当Follower收到Leader的proposal信息之后,队列化操作,立即发送ACK确认信息.对于Leader而言如果指定的zxid所对应的proposal对大部分Follower确认,则本地执行然后在队列中移除此proposal.
13) Leader.COMMIT 当Leader的操作队列头部的packet,收到”多数派”ACK消息之后,将会基于此变更的packet,向Follower交付COMMIT,此后接收到此消息的Follower开始本地执行变更操作.如果Follower在发送ACK之后,没有收到COMMIT,将一直阻塞,直到收到,如果不幸消息丢失或者当前接收到的COMMIT消息的ZXID不是Follower”等待”的zxid,将导致Follower异常退出,重新连接(重新同步).
14) Leader.PING leader和follower之间互相用来心跳感应,检测server存活性感知的信号.Follower也有主动向leader发送ping的线程,leader也有(在LeanderHandler中,会间歇性的发送ping).如果follower丢失了leader的连接,将会导致”选举”(在ping一定次数之后).如果leader丢失了follower连接,将有可能释放相应的IO资源.
15) Leader.REVALIDATE 此类型比较特殊,当Follower接受到一个client新连接时且client指定了sessionId,那么follower需要重新向Leader发送此session的校验信息(sessionId),用来决定session是否真正无效,如果无效进而移除session信息和绑定的watches. 如果leader检测有效,将会把sessionId与follower绑定(即设定session.owner(follower));同时leader向此follower回馈校验结果,follower将会初始化与session有关的数据以及IO操作.
Follower之所以这么做,是因为,当一个session(每个client一个session)断开连接之后,尝试再次建立连接,在连接断开期间,极有可能session已经失效(session失效过程有Leader维护),对于当前follower而言此sessionId不在本地session集合中(已经失效或者此Client是从其他server重连的),那么follower将无法决定究竟在Global范围内session是否有效,那么它只能向Leader确认.
16) Leader.SYNC 基于客户端的指令操作,期望在leader和follower之前刷新一下通道,经过查看,内部似乎没有做太多的工作,仅仅是此空packet往返了一次.难道是”标记”作用?查看通讯速度?
17) Leader.INFORM 这个是为Observer专门享有的消息类型,因为对于Observer将不会对数据的提议有参与的权利,所以只有当Leader确认一个消息被正确执行之后,leader将会向所有的Observer发送一个INFORM类型的packet,当然此packet的内容,就是刚刚被执行成功的”变更”;对于Observer而言,将会忽略几乎所有类型的消息,只会对INFORM类型的消息做数据接收操作.
ZK Cluster与Client通讯的操作,来自ZooDefs.Opcode:
1) Opcode.create 节点创建操作,产生于Zookeeper.create(..)
2) Opcode.delete 节点删除操作,产生于Zookeeper.delete(..)
3) Opcode.exists 节点检测操作,产生于Zookeeper.exists(..)
4) Opcode.getData 获取节点挂载的数据,产生于Zookeeper.getData(…)
5) Opcode.setData 重置节点数据,产生于Zookeeper.setData(…)
6) Opcode.getChildren 获取指定节点的子节点列表,产生于Zookeeper.getChildren(…)
7) Opcode.sync 同步操作,产生于Zookeeper.sync(…)
8) Opcode.createSession 客户端新建回话,伴随着Zookeeper客户端实例的创建.
9) Opcode.closeSession 客户端关闭回话,产生于Zookeeper.close()
[引导,选举过程]