Zookeeper 源码解读系列,集群模式(四)

前情提要

我们用了三篇博客详细讲解了Zookeeper集群模式启动的时候是如何加载配置文件,如何开启socket,以及怎么进行数据同步的。这些做完以后,我们就可以启动了,但是启动之前还有领导者选举没有讲解,这部分我们留到后面专门去讲解。领导者选举也是一个大篇章,其中涉及到的知识点十分繁杂,所以放到最后。那么本篇就是集群模式服务端启动的后续了,在这一块中也会涉及到处理器链的知识,不了解的同学可以看【单机系列三】做个复习。本篇也会被收录到【Zookeeper 源码解读系列目录】中。

集群的初始化启动

服务器运行到这里,数据同步已经结束了,我们马上要初始化了。要说一句的是到目前为止,我们还没有做RequestProcessor的初始化,之前做的一系列准备,包括我们以后要讲的选举,都是为了这个目的做的。这些流程不走完,RequestProcessor没有初始化,客户端是连不上集群的。所以我们这一篇要讲解的其实就是集群模式下面,怎么接收客户端的请求。

大家还能记得到这里,我们是从哪里进来的吗?这里已经跳的太远了,我们其实是从Leader.lead()方法进入的,这已经是【集群系列一】里的内容了。为了让思路更加明朗,我们还是从头来:

----入口----> QuorumPeerMain.main();
----转到----> QuorumPeerMain.initializeAndRun(args);
----转到----> QuorumPeerMain.runFromConfig(config);
----转到----> quorumPeer.start();
----转到----> QuorumPeer.run();
----转到----> Leader.lead();

所以回到Leader.lead()方法,往下

void lead() throws IOException, InterruptedException {
    /**略**/
    try {
        /**略**/
        waitForEpochAck(self.getId(), leaderStateSummary);
        self.setCurrentEpoch(epoch);
        try {
            waitForNewLeaderAck(self.getId(), zk.getZxid());
        } catch (InterruptedException e) {
            /**Exceptions**/
        }
        startZkServer();
        /**略**/
    } finally {
        zk.unregisterJMX(this);
    }
}

接着【集群系列一】里的在lead()方法里面讲的内容,拿到新的Epoch的返回和NewLeaderack返回。再往下就是startZkServer();,这里就是leader开始初始化了。同时follower其实也是做了初始化的,是在syncWithLeader(newEpochZxid);中跳出循环以后开始初始化的。具体是位置在Learner类中,我们上篇说到leader数据同步完成以后会发送一个"UPTODATE"的操作码告诉follower数据同步完毕了,然后跳到outerLoop跳出循环,就是这个循环往下就调用了zk.startup();开始初始化,这点我已经在当时的代码中做了注释。这里其实也是说明,我们服务端启动是在做完上述所有的事情以后才开始正式启动的。那么我们回到Leader端来,其实startZkServer();这里调用的也是zk.startup(),毕竟leaderfollower的区别就在于经过选举的结果不同,代码都是一样的,逻辑也是差不多的。而且这里接收消息的逻辑和单机模式一样,服务端接收客户端的请求最核心的就是RequestProcess那些处理器链,但是又和单机模式有些区别。

那么我们进入startZkServer();方法看看集群和单机两种不同模式之间的区别在哪里:

private synchronized void startZkServer() {
    lastCommitted = zk.getZxid();
    LOG.info("Have quorum of supporters, sids: [ "
            + getSidSetString(newLeaderProposal.ackSet)
            + " ]; starting up and setting last processed zxid: 0x{}",
            Long.toHexString(zk.getZxid()));
    zk.startup(); //一样的启动方法
    self.updateElectionVote(getEpoch());
    zk.getZKDatabase().setlastProcessedZxid(zk.getZxid());
}

进入以后就看到zk.startup();服务器启动被调用,接着进入这个方法:

public synchronized void startup() {
    if (sessionTracker == null) {//sessionTracker:Session追踪器
        createSessionTracker();
    }
    startSessionTracker();
    setupRequestProcessors();//设置请求处理器
    registerJMX();
    setState(State.RUNNING);
    notifyAll();
}

点进去发现这个和单机模式没有任何区别嘛,都是调用的setupRequestProcessors()。其实没有错,因为到这一步单机模式和集群模式调用的都是startup(),但是不一定的地方就是在请求处理器的这个方法上setupRequestProcessors();。哪里不一样呢?是调用这个方法的实例对象不一样,如果你不仔细看,就很容进入错误的方法。单机模式下,我们进入的就是ZooKeeperServer.startup(),你如果直接点进去那是没有问题的。而我们在Leader这个类中用的其实是LeaderZooKeeperServer类的实例,这个类又是继承自QuorumZooKeeperServer,在往上继承就是ZooKeeperServer,所以直接进入肯定会到父类ZooKeeperServer里面。所以要找的应该是LeaderZooKeeperServer.setupRequestProcessors()这样才能看到集群和单机模式的区别。当然跟随着和观察者也有相应的方法,分别在FollowerZooKeeperServerObserverZooKeeperServer中。

Leader的处理器链

所以我们进入LeaderZooKeeperServer.setupRequestProcessors()方法:

protected void setupRequestProcessors() {
    RequestProcessor finalProcessor = new FinalRequestProcessor(this);
    RequestProcessor toBeAppliedProcessor = new Leader.ToBeAppliedRequestProcessor(
            finalProcessor, getLeader().toBeApplied);
    commitProcessor = new CommitProcessor(toBeAppliedProcessor,
            Long.toString(getServerId()), false,
            getZooKeeperServerListener());
    commitProcessor.start();
    ProposalRequestProcessor proposalProcessor = new ProposalRequestProcessor(this,
            commitProcessor);
    proposalProcessor.initialize(); //这里要注意
    firstProcessor = new PrepRequestProcessor(this, proposalProcessor);
    ((PrepRequestProcessor)firstProcessor).start();
}

对于leader 的处理链我们先梳理下这个链是怎么走的,看下代码发现第一个firstProcessor还是PrepRequestProcessor 这个和单机是一样的;它的nextProposalRequestProcessor这个是个新的处理器;再nextCommitProcessor也是一个新的;再nextToBeAppliedRequestProcessor;最后才是FinalRequestProcessor。这里要注意到有一句proposalProcessor.initialize();,点进去看下这里是什么:

public void initialize() {
    syncProcessor.start();
}

进入后发现只有一句话syncProcessor.start();,所以其实这里SyncRequestProcessor其实还是在用的,而且单独开启了一个线程。这个线程在哪里用呢?看到initialize()ProposalRequestProcessor的类对象调起来的那么我们进入它的构造方法看看有没有:

public ProposalRequestProcessor(LeaderZooKeeperServer zks,
        RequestProcessor nextProcessor) {
    this.zks = zks;
    this.nextProcessor = nextProcessor;
    AckRequestProcessor ackProcessor = new AckRequestProcessor(zks.getLeader());
    syncProcessor = new SyncRequestProcessor(zks, ackProcessor);
}

果然就是在这里,不仅构造了SyncRequestProcessor这个处理器,而且Sync处理器还有下一个处理器AckRequestProcessor,所以梳理一下Leader就是一个多线程的链,分为两条:

链条一:
firstProcessor=PrepRequestProcessor.next ---> ProposalRequestProcessor.next ---> CommitProcessor.next ---> ToBeAppliedRequestProcessor.next ---> FinalRequestProcessor
链条二:
SyncRequestProcessor.next->AckRequestProcessor

Follower的处理器链

说完leader的链条,我们再去看下follower是怎么样的。

protected void setupRequestProcessors() {
    RequestProcessor finalProcessor = new FinalRequestProcessor(this);
    commitProcessor = new CommitProcessor(finalProcessor,
            Long.toString(getServerId()), true,
            getZooKeeperServerListener());
    commitProcessor.start();
    firstProcessor = new FollowerRequestProcessor(this, commitProcessor);
    ((FollowerRequestProcessor) firstProcessor).start();
    syncProcessor = new SyncRequestProcessor(this,
            new SendAckRequestProcessor((Learner)getFollower()));
    syncProcessor.start();
}

同样的分析,Follower也是一个多线程的链,链条一:
firstProcessor=FollowerRequestProcessor.next-> CommitProcessor.next-> FinalRequestProcessor
也单独开了一个Sync链,链条二:
SyncRequestProcessor.next->SendAckRequestProcessor

Observer的处理器链

最后我们看下Observer的链条又长什么样子:

protected void setupRequestProcessors() {      
    RequestProcessor finalProcessor = new FinalRequestProcessor(this);
    commitProcessor = new CommitProcessor(finalProcessor,
            Long.toString(getServerId()), true,
            getZooKeeperServerListener());
    commitProcessor.start();
    firstProcessor = new ObserverRequestProcessor(this, commitProcessor);
    ((ObserverRequestProcessor) firstProcessor).start();
    if (syncRequestProcessorEnabled) {
        syncProcessor = new SyncRequestProcessor(this, null); //next为null,没有后续处理器
        syncProcessor.start();
    }
}

进入代码,能看到主要的区别就在于Observer的处理器里面,控制了SyncProcessor是否要进行持久化和打快照if (syncRequestProcessorEnabled),这个if判断决定了Observer是否启动SyncProcessor。如果说syncRequestProcessorEnabledtrue那么就会持久化。如果是falsesyncProcessor对象就不会实例化,也不会启动,就不会进行持久化和打快照。比如说get命令就不需要Sync做持久化。这部分单机已经讲过了,在碰见就一笔带过不会重点讲了。
那么总结来说Observer的处理器链就是这个样子:
链条一:
firstProcessor=ObserverRequestProcessor.next->CommitProcessor.next->FinalRequestProcessor
链条二(有选择的,且没有后续):
if 成立 ----> SyncRequestProcessor
if 不成立 ----> null

处理器的功能

那么我们大体讲解一下各个处理器都是做什么事情的:

处理器名称作用
PrepRequestProcessorcheckACL,构造txn,已经讲过
SyncRequestProcessor持久化事务(txn对象),打快照,已经讲过
FinalRequestProcessor更新内存,返回response给客户端,已经讲过
ProposalRequestProcessor投票
CommitProcessor提交
AckRequestProcessor返回确认消息,不重要
ToBeAppliedRequestProcessor中转投票消息,不重要

Leader 处理器链的流程

PrepRequestProcessor & ProposalRequestProcessor

那么我们先去看leader的处理器链是怎么走的。当然要先启动PrepRequestProcessor处理器,直接进入run()方法。这个处理器已经在单机模式下讲过,这里用的是同一个,所以不多啰嗦了。进入以后,点进去 pRequest(request);方法,然后直接拉到最后,找到nextProcessor.processRequest(request)。那么这nextProcessor是谁呢?我们之前讲过nextProcessor是在这个类被初始化的时候传入进去的,所以集群模式下的Leader里面PrepRequestProcessor的下一个就是ProposalRequestProcessor。所以我们找到ProposalRequestProcessor.processRequest(request)看下这里面又写了什么:

public void processRequest(Request request) throws RequestProcessorException {
    //判断是不是learner的sync请求
    if(request instanceof LearnerSyncRequest){ 
        zks.getLeader().processSync((LearnerSyncRequest)request);
    } else {//不是,就走这里
            nextProcessor.processRequest(request); //交给下一个处理器
        if (request.hdr != null) {
            try {
                zks.getLeader().propose(request);
            } catch (XidRolloverException e) {
                throw new RequestProcessorException(e.getMessage(), e);
            }
            syncProcessor.processRequest(request);
        }
    }
}

进入后发现如果不是learner的同步请求,就直接走到了else里面,但是这里面什么逻辑都没有,直接交给了下一个请求处理器nextProcessor.processRequest(request);,也就是CommitProcessor直接提交了这个请求。这就不太对了,因为我们之前已经说过好几遍了:如果一个create之类的请求进来,肯定是要先发起来提议的,经过投票以及返回ACK确认以后才会提交。怎么就会直接到了CommitProcessor里面去提交这个请求了呢?那么我们打开CommitProcessor.processRequest(request)一起对比看下。

CommitProcessor
    synchronized public void processRequest(Request request) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Processing request:: " + request);
        }
        if (!finished) {
            queuedRequests.add(request); //加入队列
            notifyAll();
        }
    }

进入后,首先会加到queuedRequests队列里面来。这里要注意:我们这里是多线程,所以时时刻刻要记得这些线程早就已经在运行了。同样commitProcessor.start();也早就在setupRequestProcessors()里面调用了,所以一旦加进来就会立刻到run()方法里执行。这里就要转去CommitProcessor.run()里面:

public void run() {
    try {
        Request nextPending = null;            
        while (!finished) {//1.此时queuedRequests里面只有一个create
            int len = toProcess.size();//1.这里0;2.这里还是0
            for (int i = 0; i < len; i++) {//i < len 1.跳过;2.这里跳过
                nextProcessor.processRequest(toProcess.get(i));
            }
            toProcess.clear();//1.清空;2.清空
            synchronized (this) {
                //1.判定false,跳过
                //2.判定ture,进入
                if ((queuedRequests.size() == 0 || nextPending != null)
                        && committedRequests.size() == 0) {
                    wait();//wait
                    continue;
                }
                if ((queuedRequests.size() == 0 || nextPending != null)
                        && committedRequests.size() > 0) {//1.false跳过
                    Request r = committedRequests.remove();
                    if (nextPending != null
                            && nextPending.sessionId == r.sessionId
                            && nextPending.cxid == r.cxid) {
                        nextPending.hdr = r.hdr;
                        nextPending.txn = r.txn;
                        nextPending.zxid = r.zxid;
                        toProcess.add(nextPending);
                        nextPending = null;
                    } else {
                        toProcess.add(r);
                    }
                }
            }
            if (nextPending != null) {//1.nextPending==null跳过
                continue;
            }
            synchronized (this) {
                while (nextPending == null && queuedRequests.size() > 0) {//1.判定为true
                    Request request = queuedRequests.remove(); //取出request
                    switch (request.type) {//1.是create事务所以到下面给nextPending赋值
                    case OpCode.create:
                    case OpCode.delete:
                    case OpCode.setData:
                    case OpCode.multi:
                    case OpCode.setACL:
                    case OpCode.createSession:
                    case OpCode.closeSession:
                        nextPending = request;//1.nextPending赋值,break while,到第二圈去
                        break;
                    case OpCode.sync:
                        if (matchSyncs) {
                            nextPending = request;
                        } else {
                            toProcess.add(request);
                        }
                        break;
                    default:
                        toProcess.add(request);
                    }
                }
            }
        }
    } catch (InterruptedException e) {
        LOG.warn("Interrupted exception while waiting", e);
    } catch (Throwable e) {
        LOG.error("Unexpected exception causing CommitProcessor to exit", e);
    }
    LOG.info("CommitProcessor exited loop!");
}

这里的代码比较绕,所以假设我们进来一个create事务,看看代码会怎么走。此时这里面的几个队列只有queuedRequests有值。那么第一个for循环判断i < len不成立就不会进入。紧接着的第一个if条件判断queuedRequests.size() == 0 判断falsenextPending != null判定false,两者或运算结果为falsecommittedRequests.size() == 0判定ture,经过与运算,整体判定为false跳过。那么紧着的if ((queuedRequests.size() == 0 || nextPending != null) && committedRequests.size() > 0)整体判定为false跳过。再接着往下if (nextPending != null)判定为false跳过。下一个循环while (nextPending == null && queuedRequests.size() > 0)这里就是整体为ture进入循环。首先取出request = queuedRequests.remove();,我们假设的是create事务所以到下面给nextPending赋值nextPending = request;,然后break出去。此时queuedRequests.size()==0所以这个while循环一并跳出。

那么我们进入 while (!finished)的第二圈。第一个for循环判断i < len依然不成立不会进入。下面的if语句queuedRequests.size() == 0判断falsenextPending != null判定true,两者或运算结果为truecommittedRequests.size() == 0判定ture,经过与运算,整体判定为true进入。进入这个if语句后大家看做了什么事情:被wait();住了。

那么我这里想说明一个什么事情呢?这就说明一旦提交一个请求以后,其实并不会立刻被提交,而是会被阻塞住,并不会立刻执行。那么既然阻塞了,就必然会被唤醒。所以就得清楚我们等的是什么,之前我们分析过了,leader如果收到一个事务请求,要发起提议给其他follower投票,投票经过半以后才能提交执行,这就保证了数据一致性,那么这里一定等待的就是提议投票的结果。

所以我们回到ProposalRequestProcessor.processRequest(request)看看到提交的时候被阻塞之后又进行了什么逻辑:

public void processRequest(Request request) throws RequestProcessorException {
    //判断是不是learner的sync请求
    if(request instanceof LearnerSyncRequest){ 
        zks.getLeader().processSync((LearnerSyncRequest)request);
    } else {//不是,就走这里
        nextProcessor.processRequest(request); //交给下一个处理器,被阻塞
        //说明一下,如果是`get`或者`ping`,就不会有事务头。
        if (request.hdr != null) {
            try {
                zks.getLeader().propose(request);
            } catch (XidRolloverException e) {
                throw new RequestProcessorException(e.getMessage(), e);
            }
            syncProcessor.processRequest(request);
        }
    }
}

接着走我们假设的是create命令,这是个事务命令request.hdr != null这句话肯定成立,进入这个if语句。这里我们就发现zks.getLeader().propose(request);这个方法,这里就是提议进行的方法:

public Proposal propose(Request request) throws XidRolloverException {
    /**不相关,略**/
    byte[] data = SerializeUtils.serializeRequest(request);
    proposalStats.setLastProposalSize(data.length);
    //把请求包装成一个packet
    QuorumPacket pp = new QuorumPacket(Leader.PROPOSAL, request.zxid, data, null);
    Proposal p = new Proposal();
    p.packet = pp;
    p.request = request;
    synchronized (this) {
        /**Log4j**/
        lastProposed = p.packet.getZxid();
        outstandingProposals.put(lastProposed, p);//发送的proposal放到这里队列里
        sendPacket(pp);//发送出去
    }
    return p;
}

进入看到pp = new QuorumPacket(Leader.PROPOSAL, request.zxid, data, null);请求被封装成一个packet,注意这里的type就是Leader.PROPOSAL提议了,data也被封装了进去。按道理这里发出去了,follower就应该接收到这个proposal。往下把提议放到outstandingProposals.put(lastProposed, p)队列里去,这个队列我们后面还会在Leader类里用到,然后发送出去sendPacket(pp);。这里其实就是Zookeeper的提议的过程,在这个方法里leader发提议发给了各个follower。这里没有任何的阻塞,我们就直接返回出去到了syncProcessor.processRequest(request);开始调用持久化了。

SyncRequestProcessor & AckRequestProcessor

SyncProcessor我们已经说过了,所以我们就直接去SyncProcessor的下一步AckRequestProcessor里面看看,sync持久化后,又做了什么。根据刚才我们分析的处理器链逻辑SyncRequestProcessor的后面一定是AckRequestProcessor.processRequest()。也就是说,持久化结束了,才会做这个ack。所以我们去AckRequestProcessor.processRequest(request)里面看下这个处理器做了什么:

public void processRequest(Request request) {
    QuorumPeer self = leader.self;
    if(self != null)
        leader.processAck(self.getId(), request.zxid, null);
    else
        LOG.error("Null QuorumPeer");
}

我们注意到leader.processAck(self.getId(), request.zxid, null);从名字上看,这个应该是处理ack的。那么有一个问题,这个ack是哪里来的呢?一定是follower发过来的对不对。这个时候我们就可以要去看下follower那边是怎么处理的。这里先不忙去看,因为这里的篇幅已经太长了,我们留到下一篇讲解。所以我们总结一下,到目前为止经历了什么。

总结

首先我们开始调用zk.startup()启动Zookeeper。然后讲解了LeaderFollowerObserver各自的处理器链是什么样子的。在后面以一个create命令为例子讲解了,当leader接收到了一个事务命令以后,在处理器链的流程是怎么走的。总结来说就是下面的流程:

Leader收到命令,交给 ----->
PrepRequestProcessor做权限认证,创建事务等等,然后交给 ----->
ProposalRequestProcessor 发给 -----> CommitProcessor阻塞住
				  -----> 接着zks.getLeader().propose(request);发起提议,然后交给  -----> 
SyncRequestProcessor做持久化,打快照,然后交给 ----->
AckRequestProcessor处理ACK

那么我们还剩下的内容主要有三大块:第一是follower收到提议以后做出的反应是什么。第二是leader后续的处理器链是怎么执行的。第三是当follower收到一个写请求的时候,又是怎么处理的。谢谢大家看到这里,我们下一篇再见。
集群模式leader处理器链1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值