目录
主线程QuorumPeer
在启动脚本zkServer.cmd指定了启动类是QuorumPeerMain, 它会开启线程QuorumPeer#run的执行。
正常情况下, 该主线程会while循环体内持续处理各自角色的逻辑,当然也是有多种用途的异步线程来配合完成。
当Exception了 或 判定失败跳出while循环体 将进入finally语句块:设置状态为LOOKING,进入选主阶段!
while (running) {
switch (getPeerState()) {
case LOOKING:
LOG.info("LOOKING");
if (Boolean.getBoolean("readonlymode.enabled")) {
...
} else {
try {
setBCVote(null);
setCurrentVote(makeLEStrategy().lookForLeader()); //选主过程
} catch (Exception e) {
LOG.warn("Unexpected exception", e);
setPeerState(ServerState.LOOKING);
}
}
break;
case OBSERVING:
try {
LOG.info("OBSERVING");
setObserver(makeObserver(logFactory));
observer.observeLeader();
} catch (Exception e) {
LOG.warn("Unexpected exception",e );
} finally {
observer.shutdown();
setObserver(null);
setPeerState(ServerState.LOOKING);
}
break;
case FOLLOWING:
try {
LOG.info("FOLLOWING");
setFollower(makeFollower(logFactory));
follower.followLeader();
} catch (Exception e) {
LOG.warn("Unexpected exception",e);
} finally {
follower.shutdown();
setFollower(null);
setPeerState(ServerState.LOOKING);
}
break;
case LEADING:
LOG.info("LEADING");
try {
setLeader(makeLeader(logFactory));
leader.lead();
setLeader(null);
} catch (Exception e) {
LOG.warn("Unexpected exception",e);
} finally {
if (leader != null) {
leader.shutdown("Forcing shutdown");
setLeader(null);
}
setPeerState(ServerState.LOOKING);
}
break;
}
}
}
在选主阶段:不通的角色有不同的ZooKeeperServer具体实现(默认state是INITIAL),对应也会有不同的消息处理链RequestProcessor。
这里Leader会实例化与其他Learner角色间通讯的ServerSocket。每一个Learner对于Leader来说最终将对应一个LearnerHandler。
- learner在与leader同步恢复快照之后(收到Leader.UPTODATE指令),LearnerZooKeeperServer#startup() 启动 -> RUNNING 。见Learner#syncWithLeader中部分代码片段:
case Leader.UPTODATE , case Leader.NEWLEADER
- Leader#lead()在确认是leader后,开启异步线程LearnerHandler来处理与Learner间的数据同步,在此期间Leader主线程将一直阻塞在#waitForNewLeaderAck。 同步快照指令都完成之后,将发出指令NEWLEADER,等到Learner对应的ACK成功(过半Learner都认为leaderZxid是LeaderZk的Zxid)后,再LeaderZooKeeperServer#startup():也会设置更新QuorumPeer里管理与客户端Socket通讯ServerCnxnFactory的ZooKeeperServer为该LeaderZooKeeperServer!LearnerHandler(Leader)也会在#waitForNewLeaderAck后给Learner发送UPTODATE指令,告知它们可以开启服务了。
在集群的服务异常时,将触发执行Learner或Leader的#shutdown()方法,都会执行以下过程。 Leader还需要关闭所有与Learner的通讯 LearnerHandler#shutdown
// set the zookeeper server to null
self.cnxnFactory.setZooKeeperServer(null); //清空绑定的zkServer
// clear all the connections
self.cnxnFactory.closeAll(); // 关闭与客户端的所有Socket连接
// shutdown previous zookeeper
if (zk != null) {
zk.shutdown(); // 停止zkServer . 状态为SHUTDOWN
}
选主、同步恢复阶段无法响应客户端的读写请求
此时的zkServer为空。NIOServerCnxnFactory#run():
当客户端连接了服务端后:
- 依旧会创建一个连接NIOServerCnxn;
- 当客户端发送指令时,服务端 NIOServerCnxn#doIO ->#readLength将判定zkServer的状态。此时肯定不是活跃的(或为空),所以抛出IOException, 随后关闭这个Socket连接。
只有当同步恢复完成后(接收到Leader的UPTODATE指令),才会重新响应客户端请求。
启动第一个zk
- 确定自己的myid 为1,把票投给自己 ,状态是Looking。
- 不停的连接2、3 zkServer来获取它们的选票
启动第二个zk
- 初始时:把票投给自己 2,状态是Looking。 收到1发出的投它自己的选票;
- 不停的连接3 zkServer, 但此时的3 还未启动;
- 因为是3个服务做集群,所以有2个达成过半一致就能确定leader。 此时经过选票比对, 2将获取LEADING
zk2日志如下:
当确定了leader时,开始同步阶段:比对并对应处理快照日志,保持主从视图一致。
zk1的日志如下:
- 接收到2投给它自己的选票, 最终确定2的领导地位,自己成为follower;
- 持续保持与3的通讯
同步恢复阶段: 接收leader的快照同步指令
启动第三个zk
刚启动时: 也是把票投给自己,Looking状态。
zk1的日志如下:
收到了3投给他自己的选票,但会放弃(因为zk1的状态是Following),并发送投给zk2的选票给zk3。
<只有在和leader无法保持socket心跳时才会重新转成Looking开始新一轮的选主>
zk2的日志如下:和zk1类似。
zk3将接收到 1、2 确认zk2才是leader的投票,所以zk3将也认可zk2是leader。
把主zk2干掉后
zk3日志如下:
1、与leader相关的通讯处理线程开始报错
2、关闭掉FollowerZooKeeperServer、各提案处理的RequestProcessor:
FollowerRequestProcessor、CommitProcessor、FinalRequestProcessor、SyncRequestProcessor。 重新进入Looking状态开启新一轮的选主。
3、最终将决定zk3为leader !
zk1的日志情况大体上如zk3一般,本文就不再贴了 。
再接着关闭zk3
此时zk1也是关闭了所有的处理线程并进入Looking。
而客户端zkCli.cmd将无法维护窗口,直接关闭了。zk1的日志显示:
Exception causing close of session 0x0: ZooKeeperServer not running
Closed socket connection for client /0:0:0:0:0:0:0:1:58193 (no session established for client)
选主、同步恢复阶段无法响应客户端的读写请求 !