前情提要
我们用了三篇博客详细讲解了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的返回和NewLeader的ack返回。再往下就是startZkServer();,这里就是leader开始初始化了。同时follower其实也是做了初始化的,是在syncWithLeader(newEpochZxid);中跳出循环以后开始初始化的。具体是位置在Learner类中,我们上篇说到leader数据同步完成以后会发送一个"UPTODATE"的操作码告诉follower数据同步完毕了,然后跳到outerLoop跳出循环,就是这个循环往下就调用了zk.startup();开始初始化,这点我已经在当时的代码中做了注释。这里其实也是说明,我们服务端启动是在做完上述所有的事情以后才开始正式启动的。那么我们回到Leader端来,其实startZkServer();这里调用的也是zk.startup(),毕竟leader和follower的区别就在于经过选举的结果不同,代码都是一样的,逻辑也是差不多的。而且这里接收消息的逻辑和单机模式一样,服务端接收客户端的请求最核心的就是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()这样才能看到集群和单机模式的区别。当然跟随着和观察者也有相应的方法,分别在FollowerZooKeeperServer和ObserverZooKeeperServer中。
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 这个和单机是一样的;它的next是ProposalRequestProcessor这个是个新的处理器;再next是CommitProcessor也是一个新的;再next是ToBeAppliedRequestProcessor;最后才是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。如果说syncRequestProcessorEnabled是true那么就会持久化。如果是false,syncProcessor对象就不会实例化,也不会启动,就不会进行持久化和打快照。比如说get命令就不需要Sync做持久化。这部分单机已经讲过了,在碰见就一笔带过不会重点讲了。
那么总结来说Observer的处理器链就是这个样子:
链条一:
firstProcessor=ObserverRequestProcessor.next->CommitProcessor.next->FinalRequestProcessor
链条二(有选择的,且没有后续):
if 成立 ----> SyncRequestProcessor
if 不成立 ----> null
处理器的功能
那么我们大体讲解一下各个处理器都是做什么事情的:
| 处理器名称 | 作用 |
|---|---|
| PrepRequestProcessor | checkACL,构造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 判断false,nextPending != null判定false,两者或运算结果为false,committedRequests.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判断false,nextPending != null判定true,两者或运算结果为true,committedRequests.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。然后讲解了Leader,Follower和Observer各自的处理器链是什么样子的。在后面以一个create命令为例子讲解了,当leader接收到了一个事务命令以后,在处理器链的流程是怎么走的。总结来说就是下面的流程:
Leader收到命令,交给 ----->
PrepRequestProcessor做权限认证,创建事务等等,然后交给 ----->
ProposalRequestProcessor 发给 -----> CommitProcessor阻塞住
-----> 接着zks.getLeader().propose(request);发起提议,然后交给 ----->
SyncRequestProcessor做持久化,打快照,然后交给 ----->
AckRequestProcessor处理ACK
那么我们还剩下的内容主要有三大块:第一是follower收到提议以后做出的反应是什么。第二是leader后续的处理器链是怎么执行的。第三是当follower收到一个写请求的时候,又是怎么处理的。谢谢大家看到这里,我们下一篇再见。

本文深入解析Zookeeper集群模式下服务端启动流程,重点阐述领导者选举前的准备工作,包括配置加载、socket开启及数据同步。通过分析Zookeeper源码,详细解释集群模式下请求处理器链的构建与工作原理,对比单机模式,揭示领导者、跟随者和观察者处理器链的差异。
&spm=1001.2101.3001.5002&articleId=107605804&d=1&t=3&u=afb179d1a08b48569bb586b5dd731938)
1351

被折叠的 条评论
为什么被折叠?



