前情提要
我们用了三篇博客详细讲解了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
收到一个写请求的时候,又是怎么处理的。谢谢大家看到这里,我们下一篇再见。