前言
一、Zookeeper是什么?
zk是一个开源的分布式应用程序协调服务器,为分布式系统一致性服务。其一致性通过基于paxos算法(就某项决议达成一致)实现的zab协议完成的。主要功能有:配置维护、域名管理、分布式同步、集群管理。
二、zk选举理论:paxos算法描述
paxos算法执行过程分为两个阶段:准备阶段prepare与接受阶段。
A、prepare 阶段
- 提案者(Proposer)准备提交一个编号为 N 的提议,于是其首先向所有表决者(Acceptor)发
送 prepare(N)请求,用于试探集群是否支持该编号的提议。
- 每个表决者(Acceptor)中都保存着自己曾经 accept 过的提议中的最大编号 maxN。当一个
表决者接收到其它主机发送来的 prepare(N)请求时,其会比较 N 与 maxN 的值。有以下
几种情况:
a) 若 N 小于 maxN,则说明该提议已过时,当前表决者采取不回应或回应 Error 的方
式来拒绝该 prepare 请求;
b) 若 N 大于 maxN,则说明该提议是可以接受的,当前表决者会首先将该 N 记录下来,
并将其曾经已经 accept 的编号最大的提案 Proposal(myid,maxN,value)反馈给提案者,
以向提案者展示自己支持的提案意愿。其中第一个参数 myid 表示该提案的提案者
标识 id,第二个参数表示其曾接受的提案的最大编号 maxN,第三个参数表示该提
案的真正内容 value。当然,若当前表决者还未曾 accept 过任何提议,则会将
Proposal(myid,null,null)反馈给提案者。
c) 在 prepare 阶段 N 不可能等于 maxN。这是由 N 的生成机制决定的。要获得 N 的值,
其必定会在原来数值的基础上采用同步锁方式增一。
B、 accept 阶段
- 当提案者(Proposer)发出 prepare(N)后,若收到了超过半数的表决者(Accepter)的反馈,
那么该提案者就会将其真正的提案 Proposal(myid,N,value)发送给所有的表决者。
- 当表决者(Acceptor)接收到提案者发送的 Proposal(myid,N,value)提案后,会再次拿出自己
曾经 accept 过的提议中的最大编号 maxN,或曾经记录下的 prepare 的最大编号,让 N
与它们进行比较,若 N 大于等于这两个编号,则当前表决者 accept 该提案,并反馈给
提案者。若 N 小于这两个编号,则表决者采取不回应或回应 Error 的方式来拒绝该提议。
- 若提案者没有接收到超过半数的表决者的 accept 反馈,则有两种可能的结果产生。一
是放弃该提案,不再提出;二是重新进入 prepare 阶段,递增提案号,重新提出 prepare
请求。
- 若提案者接收到的反馈数量超过了半数,则其会向外广播两类信息:
a) 向曾 accept 其提案的表决者发送“可执行数据同步信号”,即让它们执行其曾接收
到的提案;
b) 向未曾向其发送 accept 反馈的表决者发送“提案 + 可执行数据同步信号”,即让
它们接受到该提案后马上执行。
提问 1:
在Prepare阶段已经比较过了,并且已经通过了,为什么在Accept阶段还需要进行比较?
若在Accept阶段有新的提案提出,此新的提案prepare(N)N的值肯定比该Accept阶段的本地MaxN值大。
提问 2:
在 Prepare 阶段与 Accept 阶段都进行了比较,为什么在发送 COMMIT 信号量时无需进
行比较?
若commit后,会将server标记为leaner,则此server不接受提案,也不会发起提案。
三、Leader选举过程
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 {
//1.选举前初始化工作
HashMap<Long, Vote> recvset = new HashMap<Long, Vote>();//外部选票
HashMap<Long, Vote> outofelection = new HashMap<Long, Vote>();//非法选票
int notTimeout = finalizeWait;
//2.我选我
synchronized(this){
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
*/
//3.比较本轮选举和来自外部的选票
while ((self.getPeerState() == ServerState.LOOKING) &&
(!stop)){
/*
* Remove next notification from queue, times out after 2 times
* the termination time
* 选票通知队列
*/
Notification n = recvqueue.poll(notTimeout,
TimeUnit.MILLISECONDS);
/*
* Sends more notifications if haven't received enough.
* Otherwise processes new notification.
*/
if(n == null){
//判断当前server与集群是否失联,false:失联
if(manager.haveDelivered()){
//当前server与集群未失联,但还未选出leader,则重新发送当前server选票通知,
//目的是为了接收其他server的选票信息(因为n=null,recvqueue已经没有选票了)
sendNotifications();
} else {
//当前server与集群已失联,而不用重新发送选票通知
//因为如果集群还未选出leader,其他server就会执行上个逻辑即if(manager.haveDelivered()){sendNotifications();}
//这样当前server就能收到选票通知了
manager.connectAll();
}
/*
* Exponential backoff
*/
int tmpTimeOut = notTimeout*2;
notTimeout = (tmpTimeOut < maxNotificationInterval?
tmpTimeOut : maxNotificationInterval);
LOG.info("Notification time out: " + notTimeout);
}
//校验选票合法性
else if(validVoter(n.sid) && validVoter(n.leader)) {
/*
* Only proceed if the vote comes from a replica in the
* voting view for a replica in the voting view.
*/
switch (n.state) {
case LOOKING:
// If notification > current, replace and send messages out
// 来自外部的选票epoch比较大,说明自己本轮的选票已过时
if (n.electionEpoch > logicalclock.get()) {
logicalclock.set(n.electionEpoch);
//清空票箱
recvset.clear();
//判断当前server与n,谁更适合做leader
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()) {
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;
} else if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
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));
//4.结束本轮选举
//当前server选票信息在票箱中的支持率已过半,则
if (termPredicate(recvset,
new Vote(proposedLeader, proposedZxid,
logicalclock.get(), proposedEpoch))) {
// Verify if there is any change in the proposed leader
//while有两个出口:
//1) break:说明剩下的recvqueue队列中找到了比当前server的推荐选票
//2) while循环条件:说明剩下的recvqueue队列中没找到比当前server的推荐选票
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
*/
//n为null,剩下的recvqueue队列中没找到比当前server的推荐选票
//当前server所推荐选票是最适合的
if (n == null) {
self.setPeerState((proposedLeader == self.getId()) ?
ServerState.LEADING: learningState());
Vote endVote = new Vote(proposedLeader,
proposedZxid,
logicalclock.get(),
proposedEpoch);
leaveInstance(endVote);
return endVote;
}
}
break;
//5.不是选举状态的处理
case OBSERVING:
LOG.debug("Notification from observer: " + n.sid);
break;
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.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 {
if (!validVoter(n.leader)) {
LOG.warn("Ignoring notification for non-cluster member sid {} from sid {}", n.leader, n.sid);
}
if (!validVoter(n.sid)) {
LOG.warn("Ignoring notification for sid {} from non-quorum member sid {}", n.leader, 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());
}
}