选举信息-选举流程-选举场景-源码分析
1 选举信息
<1> 服务器角色信息
在Zookeeper集群提供服务时,集群中角色如下:
- Leader:一个Zookeeper集群同一时间只有一个Leader。所有的写操作必须要通过Leader完成,再由Leader将写操作广播给其它服务器。
- Follower:一个Zookeeper集群可以存在多个Follower。Follower可直接处理并且返回客户端的读请求,同时会将写请求转发给Leader处理,并且负责在Leader处理写请求时对请求进行投票。另外Follower可以参与竞选Leader。
- Observer:Observer功能与Follower类似,但是没有投票权,也不会参与竞选Leader。
<2> 服务器状态信息
- Looking:寻找Leader状态。当服务器处于该状态时,会认为当前集群中没有Leader,因此需要进入Leader选举流程
- Following:跟随者状态,表明当前服务器角色是Follower
- Leading:领导者状态,表明当前服务器角色是Leader
- Observing:观察者状态,表明当前服务器角色是Observer
<3> 投票信息
- leader:被选举的Leader的sid
- zxid:被选举的Leader的事务id
- sid:当前服务器的sid
- electionEpoch:当前投票的轮次
- peerEpoch:当前服务器的Epoch
选票PK:
(1)选票中Epoch大的优先级高;
(2)选票中Zxid的大的优先级高;
(3)选票中Sid大的优先级高;
选票终止条件:
以某一选票数占集群中参与竞选节点(除Observer外)数量的一半以上,选举结束;
2 选举流程
3 选举场景
<1> 票箱信息
票箱信息:保存选举的服务器SID和被选举的服务器SID,即(sid,leader);例如集群中节点为SID=1的服务器选举节点为SID=3的服务器,则票箱信息为(1,3)
<2> 选票信息
选票信息为(electionEpoch,leader,zxid),分别代表选举的轮次、被选举服务器的SID,被选举服务器的zxid
<3> 初始启动选举
(1)每个Server发出一个投票,初始情况,Server都会将自己作为Leader服务器来进行投票,比如Server1会发出(1,1,0)选票(表示epoch为1,选举leader的sid为1,并且被选举的服务器zxid为0),然后各自将这个投票发给集群中其他机器,Server1的票箱信息为(1,1)(表示投票的服务器sid为1,选举的leader的sid为1)
(2)接受来自各个服务器的投票。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票,是否来自Looking状态的服务器。
(3)处理投票。针对每一个投票,服务器需要将别人的票和自己的票进行PK,pk规则如上所示。
(4)统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息。
(5)改变服务器状态。一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为LEADING。
<4> Follower重启
<5> 运行期间选举 leader宕机
与上面相比,会在开始添加一个步骤【变更状态】。
Leader挂后,余下的非Observer服务器都会讲自己的服务器状态变更为LOOKING,然后开始进入Leader选举过程。
4 源码分析
4.1 类图关系
4.1 FastLeaderElection
<1> Notification
Notification表示收到的选举投票信息(其他服务器发来的选举投票信息),其包含了被选举者的id、zxid、选举周期等信息,其buildMsg方法将选举信息封装至ByteBuffer中再进行发送。
<2> ToSend
ToSend表示发送给其他服务器的选举投票信息,也包含了被选举者的id、zxid、选举周期等信息。
<3> Messenger
其中 WorkerReceiver:
选票接收器,不断地从QuorumCnxManager中获取其他服务器发来的选举消息,并将其转换成一个选票,然后保存到recvqueue中
其中 WorkerSender:
选票发送器,其会不断地从sendqueue中获取待发送的选票,并将其传递到底层QuorumCnxManager中,其过程是将FastLeaderElection的ToSend转化为QuorumCnxManager的Message
4.2 QuorumCnxManager
该类有四个内部类:
SendWorker类,Message类,RecWorker类,Listener类
<1> SendWorder
这个类作为“发送者”,继承ZooKeeperThread,线程不断地从发送队列取出,发送给对应sid的机器。
Long sid; //目标机器sid,不是当前机器sid
Socket sock;
RecvWorker recvWorker; //该sid对应的RecWorker
volatile boolean running = true;
DataOutputStream dout;
<2> Message
static public class Message {
Message(ByteBuffer buffer, long sid) {
this.buffer = buffer;
this.sid = sid;
}
ByteBuffer buffer;
long sid;
}
sid为消息来源方的sid,buffer即指消息体
<3> RecvWorker
Long sid;
Socket sock;
volatile boolean running = true;
final DataInputStream din;
final SendWorker sw;
4.3 FastLeaderElection中的lookForLeader()
public Vote lookForLeader() throws InterruptedException {
try {
self.jmxLeaderElectionBean = new LeaderElectionBean();
MBeanRegistry.getInstance().register(
self.jmxLeaderElectionBean, self.jmxLocalPeerBean);
} catch (Exception e) {
LOG.warn("Failed to register with JMX", e);
self.jmxLeaderElectionBean = null;
}
if (self.start_fle == 0) {
self.start_fle = Time.currentElapsedTime();
}
try {
// 这是票箱的意思吗
HashMap recvset = new HashMap();
HashMap outofelection = new HashMap();
int notTimeout = finalizeWait;
synchronized(this){
// 逻辑时钟自增,每进行一轮新的Leader选举,都需要更新逻辑时钟
logicalclock.incrementAndGet();
// 更新选票(初始化选票)
updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());
}
LOG.info("New election. My id = " + self.getId() +
", proposed zxid=0x" + Long.toHexString(proposedZxid));
// 广播选票
sendNotifications();
/*
* Loop in which we exchange notifications until we find a leader
*/
// 循环中(当前服务器处于LOOKING)
while ((self.getPeerState() == ServerState.LOOKING) &&
(!stop)){
/*
* Remove next notification from queue, times out after 2 times
* the termination time
*/
// 从接受到的选票队列中拿取一个 Notification
Notification n = recvqueue.poll(notTimeout,
TimeUnit.MILLISECONDS);
/*
* Sends more notifications if haven't received enough.
* Otherwise processes new notification.
*/
// 接受到的选票队列中无选票
if(n == null){
// 如果发往各个服务器的消息队列都为空
if(manager.haveDelivered()){
// 广播选票
sendNotifications();
} else {
// 存在未发送的消息,遍历与服务器进行连接
manager.connectAll();
}
/*
* Exponential backoff
*/
int tmpTimeOut = notTimeout*2;
notTimeout = (tmpTimeOut < maxNotificationInterval?
tmpTimeOut : maxNotificationInterval);
LOG.info("Notification time out: " + notTimeout);
}
// 选票接收队列中存在选票并且该选票的发送者是有资格投票的
else if(self.getVotingView().containsKey(n.sid)) {
/*
* Only proceed if the vote comes from a replica in the
* voting view.
*/
// 判断发送选票的服务器的状态
switch (n.state) {
case LOOKING: // 处于寻找leader状态
// If notification > current, replace and send messages out
// 发出选票的服务器epoch大于本服务器的逻辑时钟
if (n.electionEpoch > logicalclock.get()) {
// 改变逻辑时钟的值
logicalclock.set(n.electionEpoch);
// TODO 清空票箱
recvset.clear();
// 进行选票pk
if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
getInitId(), getInitLastLoggedZxid(), getPeerEpoch())) {
updateProposal(n.leader, n.zxid, n.peerEpoch);
} else {
updateProposal(getInitId(),
getInitLastLoggedZxid(),
getPeerEpoch());
}
// 将获胜的选票进行广播
sendNotifications();
} else if (n.electionEpoch < logicalclock.get()) { // 发出选票的服务器epoch小于本服务器的逻辑时钟
if(LOG.isDebugEnabled()){
LOG.debug("Notification election epoch is smaller than logicalclock. n.electionEpoch = 0x"
+ Long.toHexString(n.electionEpoch)
+ ", logicalclock=0x" + Long.toHexString(logicalclock.get()));
}
break; // 直接跳出循环了??? 这里是结束switch循环,重新获取一张选票
} else if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch, // 发出选票的服务器epoch等于本服务器的逻辑时钟,进行pk,如果获胜则更新选票并广播
proposedLeader, proposedZxid, proposedEpoch)) {
updateProposal(n.leader, n.zxid, n.peerEpoch);
sendNotifications();
}
if(LOG.isDebugEnabled()){
LOG.debug("Adding vote: from=" + n.sid +
", proposed leader=" + n.leader +
", proposed zxid=0x" + Long.toHexString(n.zxid) +
", proposed election epoch=0x" + Long.toHexString(n.electionEpoch));
}
// 将选票放入票箱
recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));
// 判断选举是否结束
// 票箱和当前的leader选票进行比较,看是否超过半数
if (termPredicate(recvset,
new Vote(proposedLeader, proposedZxid,
logicalclock.get(), proposedEpoch))) {
// Verify if there is any change in the proposed leader
// 将选票接收队列中所有剩下的选票与选出的leader比较,如果获胜,则放入票箱,跳出while循环
while((n = recvqueue.poll(finalizeWait,
TimeUnit.MILLISECONDS)) != null){
if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
proposedLeader, proposedZxid, proposedEpoch)){
recvqueue.put(n);
break;
}
}
/*
* This predicate is true once we don't read any new
* relevant message from the reception queue
*/
// 选票接收队列中已经没有选票了
if (n == null) {
// 最后胜出的选票是自己,更新状态为leading,否则为following
self.setPeerState((proposedLeader == self.getId()) ?
ServerState.LEADING: learningState());
Vote endVote = new Vote(proposedLeader,
proposedZxid,
logicalclock.get(),
proposedEpoch);
leaveInstance(endVote);
return endVote;
}
}
break;
case OBSERVING:
LOG.debug("Notification from observer: " + n.sid);
break;
// 发送选票的服务器的状态是Following和Leading
// 这种情况是某台服务器重启之后,已经选举出新Leader了
case FOLLOWING:
case LEADING:
/*
* Consider all notifications from the same epoch
* together.
*/
// 如果是同一轮投票
if(n.electionEpoch == logicalclock.get()){ //是否可以加入已有的集群
// 将选票放入到票箱中
recvset.put(n.sid, new Vote(n.leader,
n.zxid,
n.electionEpoch,
n.peerEpoch));
if(ooePredicate(recvset, outofelection, n)) {
self.setPeerState((n.leader == self.getId()) ?
ServerState.LEADING: learningState());
Vote endVote = new Vote(n.leader,
n.zxid,
n.electionEpoch,
n.peerEpoch);
leaveInstance(endVote);
return endVote;
}
}
/*
* Before joining an established ensemble, verify
* a majority is following the same leader.
*/
// 这是一条与当前逻辑时钟不符合的消息,那么说明在另一个选举过程中已经有了选举结果,
// 于是将该选举结果加入到outofelection集合中,再根据outofelection来判断是否可以结束选举,
// 如果可以也是保存逻辑时钟,设置选举状态,退出选举过程。
outofelection.put(n.sid, new Vote(n.version,
n.leader,
n.zxid,
n.electionEpoch,
n.peerEpoch,
n.state));
if(ooePredicate(outofelection, outofelection, n)) {
synchronized(this){
logicalclock.set(n.electionEpoch);
self.setPeerState((n.leader == self.getId()) ?
ServerState.LEADING: learningState());
}
Vote endVote = new Vote(n.leader,
n.zxid,
n.electionEpoch,
n.peerEpoch);
leaveInstance(endVote);
return endVote;
}
break;
default:
LOG.warn("Notification state unrecognized: {} (n.state), {} (n.sid)",
n.state, n.sid);
break;
}
} else {
LOG.warn("Ignoring notification from non-cluster member " + n.sid);
}
}
return null;
} finally {
try {
if(self.jmxLeaderElectionBean != null){
MBeanRegistry.getInstance().unregister(
self.jmxLeaderElectionBean);
}
} catch (Exception e) {
LOG.warn("Failed to unregister with JMX", e);
}
self.jmxLeaderElectionBean = null;
LOG.debug("Number of connection processing threads: {}",
manager.getConnectionThreadCount());
}
}
}
流程图: