zookeeper选主+恢复过程 简析

文章详细描述了ZooKeeper集群中QuorumPeer主线程的工作流程,包括选主、同步恢复阶段的处理,以及在这些阶段中如何响应客户端的读写请求。在选主阶段,不同角色(如LOOKING,OBSERVING,FOLLOWING,LEADING)有不同的行为。同步恢复后,Leader会向Learner发送UPTODATE指令,允许它们开始服务。当主服务器宕机或关闭时,会触发新的选主过程,并在新Leader确定后恢复服务。在此期间,客户端的读写请求将被暂停,直到同步完成。
摘要由CSDN通过智能技术生成

目录

 

主线程QuorumPeer

选主、同步恢复阶段无法响应客户端的读写请求

启动第一个zk

启动第二个zk

启动第三个zk

把主zk2干掉后

再接着关闭zk3


主线程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。

  1.  learner在与leader同步恢复快照之后(收到Leader.UPTODATE指令),LearnerZooKeeperServer#startup() 启动 -> RUNNING 。见Learner#syncWithLeader中部分代码片段: 
    case Leader.UPTODATE , case Leader.NEWLEADER
    
  2. 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#syncWithLeader

在集群的服务异常时,将触发执行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():

当客户端连接了服务端后:

  1. 依旧会创建一个连接NIOServerCnxn;
  2. 当客户端发送指令时,服务端 NIOServerCnxn#doIO ->#readLength将判定zkServer的状态。此时肯定不是活跃的(或为空),所以抛出IOException, 随后关闭这个Socket连接。

只有当同步恢复完成后(接收到Leader的UPTODATE指令),才会重新响应客户端请求。

启动第一个zk

  1. 确定自己的myid 为1,把票投给自己 ,状态是Looking。 
  2. 不停的连接2、3 zkServer来获取它们的选票

启动第二个zk

  1. 初始时:把票投给自己 2,状态是Looking。 收到1发出的投它自己的选票;
  2. 不停的连接3 zkServer, 但此时的3 还未启动;
  3. 因为是3个服务做集群,所以有2个达成过半一致就能确定leader。 此时经过选票比对, 2将获取LEADING 

zk2日志如下:

 当确定了leader时,开始同步阶段:比对并对应处理快照日志,保持主从视图一致。


 zk1的日志如下:

  1. 接收到2投给它自己的选票, 最终确定2的领导地位,自己成为follower;
  2. 持续保持与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)

 选主、同步恢复阶段无法响应客户端的读写请求 !

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值