`
QING____
  • 浏览: 2230814 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Zookeeper选举过程描述与状态迁移

 
阅读更多

Zookeeper服务端初始化过程(二):Leader选举过程

 

Server具有如下几种状态
LOOKING:失去leader信号,选举中
FOLLOWING:环境正常,对于follower而言,正在“跟随”leader
LEADING:环境正常,对于leader而言,正在“带领”
OBSERVING:环境正常,对于observer而言,正在“观察”

 

选举的时机:Leader失效,或者Follower认为Leader"失效";比如Follower首次加入集群时无法确定Leader则尝试选举,比如Follower和Leader之间的网络问题,导致Follower离群,此时Follower也会尝试"选举"--尽管是徒劳.Leader失效的时机为:当Leader和Follower发送ping时,遇到"多数派"的Follower无法响应时(可能多数Follower已经离群,或者Leader离群),此时Leader进入LOOKING模式,开始选举.


F1.FastLeaderElection类初始过程

 

  1. QuromCnxManager是一个相对底层的”链接”处理类,他负责管理当前server和其他server的socket链接,以及socket上数据的输入输出,对于FastLeader选举过程中所造成的消息发送和接受,均由此manager处理,它维持当前server和其他server建立的链接,以及侦听选举端口上,其他server的链接申请或者数据IO;链接为socket链接,非NIO长连接."链接"的作用,就是为选举过程中所发生的数据交互进行IO操作(例如,接收选举数据和发送头片数据等).
  2. 为了避免大量server之间互相建立socket链接,造成性能问题(甚至是不必要的重复链接),manager对链接的控制采取了一种” challenge”(挑战)方式,让任意2个server之间只有一个socket链接,而且总是sid较小的server作为socket server端,sid较大的作为sock client端,在整个分布式网络中,将这种有序有向的链接逐个节点传递下去.不过在初次通信时,server会尝试和所有的其他server发起链接,对于链接的socket server端,来决定是否接受(accept)链接.如果当前server发现socket终端的sid比自己的sid小,那么当前server就会放弃已经建立的链接(假如已经建立)或者关闭此socket,并主动向sid较小的server发起socket链接.选举过程中,如果socket超时或者其他原因终端,链接将会重建.

 

F2.QuorumCnxManager内部结构

 

  1. Notification:”通知”,其他server发送的消息,包括server所提议(推选)的leader所具有的如下属性:leader(机器id),,zxid,peerEpoch;以及”提议者”的sid,serverState,electionEpoch.
  2. ToSend:”发件”,当前server需要发给其他server的消息,包括它提议的leader所具有的如下属性:leader(机器id),zxid,peerEpoch,以及当前server的如下属性:electionEpoch,serverState,sid.它和Notification相互对应.为了区别,用了2个几乎完全一样的类.
  3. FastLeaderElection类持有2个队列,sendqueue和recvqueue.sendqueue是当前server亟待发给其他server的消息(ToSend),recvqueue是当前server已经接收但还没有处理的消息(Notification).
  4. SendWorker和RecvWorker在socket建立成功后,同时被创建,实例化时同时也持有sid,dataInput或dataoutput引用,很便捷的操作socket数据.
  5. FastLeaderElection也不能脱俗,使用2个队列分别保存亟待发送的ToSend和Nofification,数据结构为LinkedBlockingQueue.
  6. QuromCnxManager持有3个数据结构,:
  • queuesendMap,亟待发送的消息,key为sid,value为一个ArrayBlockingQueue,即每个sid对应socket维护一个队列,有各自的socket负责发送;
  • senderWorkerMap,key为sid,value为SendWorker实例,已标记server是否和sid已经建立了链接,如果此sid下的SendWorker不为null,则标示链接已经建立.
  • recvQueue,一个ArrayBlockingQueue,标示server在选举端口上接收到的”提议”消息,此队列最终将交付给FastLeaderElection类来消费.
  • FastLeaderElection也不能脱俗,使用2个队列分别保存亟待发送的ToSend和Nofification,数据结构为LinkedBlockingQueue.

 

F3.FastLeaderElection选举过程

  1. 选举过程中,有几个参数容易混淆: logicalclock,这个匪夷所思的命名,意思是当前server所持有的最高选举轮数,确保自己所接受的其他server提议都处于同一轮选举上(计算投票结果才有效);Notification中有一个electionEpoch变量,标示提议者发送消息是所处于的选举轮数.a) 为了保证选举结果的有效性,所有的投票epoch必须一致,消息中较低的epoch将会被抛弃;而且server从消息中发现更高的epoch,也会将本地的logicalclock和它保持一致,并清除先前的投票集合,重新计数.b) 如果接受的”提议”消息的epoch,比本地的logicalclock要小,那么当前server会立即反馈一个ToSend,告知提议者,最新的epoch(logicalclock).
  2. 当前server已经计算出了leader,并且处于Following或者Leading状态,如果接收到的提议消息是Looking,那么次消息就是滞后的投票,或者是提议者加入集群,寻找Leader.
  3. Observer发送的投票是直接被忽略,而且server也不会向Observer发送”提议”.

 



 F4.选举过程

  1. 当server接受到的”提议”消息中,epoch大于或者等于当前server本地的logicalclock时,server会对此”提议”进行校验,如果检验成功,将会将本地持有的提议信息替换.[注意,此处为server之间信息比较,而非比较”投票”信息],依次比较epoch,zxid,sid.
     a) 如果proposalEpoch > peerEpoch,校验成功.此时的peerEpoch是整个集群server最近一次投票成功时的epoch;每次投票成功,每个server就会把epoch写入到文件.否则,参见b)
    
     b) 如果proposalEpoch = peerEpoch,并且proposalZxid > currentZxid,校验成功.currentZxid为当前server的zkDatabase中持有的最大的zxid.否则,参见c)
    
     c) 如果proposalZxid = currentZxid,并且”提议者”的sid比当前server的sid大,校验成功.否则失败.最终将由sid来决定,是否接受此次”提议”.
    
        Server本地保持者一份当前投票的最新(高)的提议副本,此副本包括epoch/zxid/leader(sid);如果server从消息中发现有更好的提议,将会替换本地的这些信息.并在适当的时机,将自己收集到的提议副本发送给其他”提议者”.知道最终达成一致.
     
  2. 如果”提议”被当前server校验通过,将被封装成一个更加简小的对象Vote(投票).”投票”将会被放入一个Map中,key为sid,value就是”投票”,确保每个server都参与而且只有一个有效的vote.
  3. “断言是否成功”:截止到目前,当前”提议”是否已经是”多数派”,如果是,即可结束投票,leader已经在集群中产生.(可以提前确定leader).
  4. Outofelection是个map,key为sid,value为vote(投票);它的本意是存储那些在非投票期间所产生的”vote”(类似于票据),在失效的server重新加入集群,将会触发”选举”,此时其他server的”提议”将会被加入到outofelection,当前server根据此map来判断是否存在”多数派”,如果集群处于”危险期”(少数派),那么新加入的server选举直到”多数派”出现.
  5. 新server加入,投票时还会进行CheckLeader操作.
if (termPredicate(outofelection, 
								new Vote(n.leader,n.zxid, n.electionEpoch, n.peerEpoch, n.state))
                                && checkLeader(outofelection, n.leader, n.electionEpoch)) {
	synchronized(this){
		logicalclock = n.electionEpoch;
		self.setPeerState((n.leader == self.getId()) ?
				ServerState.LEADING: learningState());
	}
	Vote endVote = new Vote(n.leader, n.zxid, n.peerEpoch);
	leaveInstance(endVote);
	return endVote;
}
 

 


F5.Server状态迁移

  1. 只有当前server处于LOOKING状态下,才会进行”投票”,当处于FOLLOWING或者LEADING状态时,说明集群中,Leader是存活的.选举的唯一条件就是”Leader”失效或者离群.[如果"多数派"不存在,则直接导致ZK Cluster中所有的"write"操作阻塞]
  2. “选举”所涉及到的SenderWokrer/RecvWorker线程,会一直有效.不会因为选举成功,就退出.
  3. 新的leader选举之后,leader将会首先根据epoch重置一下zxid,即zxid的前32位为epoch,后32为为自增counter,初始值为0.理论上说无论如何选举,均不会出现zxid重复的现象。
  4. Follower server/Observer server将分别有Follower类、Observer类来处理与Leader之间的数据交互,它们均继承自Learner类;在与Leader正式同步数据或者操作之前,首先会向Leader发送一个数据包,leader会反馈其此时的zxid.(registerWithLeader).
  5. 在Follower与Leader之间通讯有专一的IO通道,Leader会间歇性的发送ping来检测Follower或Observer的状态,如果发现ping不同,则直接导致Leader与Follower之间的连接关闭(关闭的后果就是,此后所有的请求都将不会波及到此Follower);Follower不会主动去"ping" Leader,不过Follower在Socket层面做了工作,Follower或在Socket上设定soTimeout,如果在read操作阻塞超时,也将直接操作IO关闭,Follower进入LOOKING模式.

[引导:Leader选举补充部分]

 

Leader:  Cluster环境中操作"调度者",负责"数据一致性"的控制,主要包括"write操作"/Session过期控制等.

Follower: "跟随者",它是和Leader共同构成Cluster多数派,直接负责接收Client的read和write操作,并转发write操作给"Leader".

Observer(“围观者”):

在ZK server中,有一种特殊的server类型,是Observer(read-only),它可以在不影响写入性能的同时扩展zk。zk之所以设计这种类型的server,是可以理解的;如果一个cluster中不断新增的voting member(follower),将会导致write性能下降,这归因于write操作需要至少半数的follower同意才能被执行,所以这种“提议/投票”的开支会随着“follower”的增加而增加。

我们引入了一个新的server类型,就是Observer;它对于解决这种问题很有帮助,而且跟一步提高了ZK的扩展性。Observer为non-voting成员,即不参与leader选举和其他操作的“提议”,它就像一个“围观者”一样只会接受投票的结果,而不是投票的协议。除了这个简单的区别之外,它和follower几乎一样。client也可以和它们建立链接,发送read和write请求。Observer和Follower一样把write请求转发给leader,只是它们然后就是简单的等待(hear)此操作的投票结果;因此我们能够在无损性能的情况下增加数个observer。

Observer还有其他的优点,因为它们不参与投票,他们不是ZK成员的核心部分;所以即使它们失效了,或者离群了,也不会对ZK 服务带来任何不利影响。Observer可以链接比followers更加不可靠的网路。

Observer类中可以看到它对各种消息的处理,Observer不是ZAB的一部分,也参与提议,在processPacket()方法中可见,observer会忽略PROPOSAL/COMMIT类型的请求,而增加了一个INFORM,这个请求类型是为Observer设计的,当proposal成功执行后,会把数据packet以INFORM消息类型发给Observer(Follower不关系此类型),那么observer即可以在本地应用最新的数据。

 

设置Observer非常简单,只需要将zoo.cfg中peerType=observer即可。

简单的使用场景:

作为数据中心桥:如果构成ZK环境有2个数据中心,那么2个数据中心的网络延迟是一个很棘手的问题,(延迟)可能会导致“假故障”问题(超时等)。不过可以将其中一个datacenter全部设置为observer,这样可以避免这些问题,而且链接observer的客户端仍然可以看到问题提议(issue proposal)。(zk cluster中一部分机器作为observer,这部分及其可能位于位置较远的其他地区,为了不让这种“位置”距离影响了zk服务,只能这这部分zk server成为observer,比如你的zk服务一部分在中国,另部分可能位于美国机房)。

 

 

 

  • 大小: 66.1 KB
  • 大小: 33.9 KB
  • 大小: 74.4 KB
  • 大小: 83.3 KB
  • 大小: 57.2 KB
分享到:
评论
1 楼 lanhz 2019-02-20  
对于三个节点:1、2、3,如果2向1广播投给2(即2自己),1回复2也投2,这时根据大多数就确定2了。如果3此时广播给1投给3(即3自己),此时假设1还没有收到2消息(确定2位leader的回复消息),那么1变更为提议3了,1给3投票,发送投票消息(投给3),此时根据大多数确定3也为leader。
这个问题,Zookeeper是怎么避免的呢?希望大神不吝赐教。

相关推荐

Global site tag (gtag.js) - Google Analytics