五、请求处理流程
5.1 处理器链的组装
上节我们知道主从数据同步之后,zookeeper服务才正式启动,启动的第一步就是处理器链的组装
,每一个客户端的请求都会经过这个处理器链上的处理器。下图是leader节点组装后的处理器链
源码如下:
@Override
protected void setupRequestProcessors() {
// 最终处理器
RequestProcessor finalProcessor = new FinalRequestProcessor(this);
RequestProcessor toBeAppliedProcessor = new Leader.ToBeAppliedRequestProcessor(
finalProcessor, getLeader().toBeApplied);
// 提交处理器
commitProcessor = new CommitProcessor(toBeAppliedProcessor,
Long.toString(getServerId()), false);
commitProcessor.start();
// 事务处理器(内部包含了同步处理器SyncRequestProcessor、ACK处理器AckRequestProcessor)
ProposalRequestProcessor proposalProcessor = new ProposalRequestProcessor(this,
commitProcessor);
proposalProcessor.initialize();
// 请求预处理器
firstProcessor = new PrepRequestProcessor(this, proposalProcessor);
((PrepRequestProcessor)firstProcessor).start();
}
5.2 请求的具体处理流程
处理请求的流程图如下:
- PrepRequestProcessor(请求预处理器):
将请求Request封装成更新记录ChangeRecord,并置入outstandingChanges(进行中的更新记录)
,并将请求转发给事务处理器 ProposalRequestProcessor(事务处理器):内部包含了同步处理器SyncRequestProcessor、ACK处理器AckRequestProcessor。将请求Request封装成Proposal事务,发送给其他follower节点,并转发给同步处理器。
SyncRequestProcessor(同步处理器):
将请求Request写入本地日志文件中
,并转发给ACK处理器
AckRequestProcessor(ACK处理器):当超过半数节点
返回该事务的ACK,提交该事务给提交处理器CommitProcessor- CommitProcessor(提交处理器):将请求Request转发给最终处理器FinalRequestProcessor
- FinalRequestProcessor(最终处理器):将请求Request更新到内存数据中,返回响应
5.2.1 请求预处理器PrepRequestProcessor
它将请求Request封装成更新记录ChangeRecord,并置入outstandingChanges,部分源码如下
org.apache.zookeeper.server.PrepRequestProcessor
protected void pRequest(Request request) throws RequestProcessorException {
// 判断请求类型
switch (request.type) {
case OpCode.create:
CreateRequest createRequest = new CreateRequest();
// 准备请求的两阶段事务(将请求封装成ChangeRecord)
pRequest2Txn(request.type, zks.getNextZxid(), request, createRequest, true);
break;
case OpCode.delete:
DeleteRequest deleteRequest = new DeleteRequest();
pRequest2Txn(request.type, zks.getNextZxid(), request, deleteRequest, true);
break;
case OpCode.setData:
SetDataRequest setDataRequest = new SetDataRequest();
pRequest2Txn(request.type, zks.getNextZxid(), request, setDataRequest, true);
break;
case OpCode.setACL:
SetACLRequest setAclRequest = new SetACLRequest();
pRequest2Txn(request.type, zks.getNextZxid(), request, setAclRequest, true);
break;
case OpCode.check:
CheckVersionRequest checkRequest = new CheckVersionRequest();
pRequest2Txn(request.type, zks.getNextZxid(), request, checkRequest, true);
break;
case OpCode.multi:
case OpCode.createSession:
case OpCode.closeSession:
pRequest2Txn(request.type, zks.getNextZxid(), request, null, true);
break;
case OpCode.sync:
case OpCode.exists:
case OpCode.getData:
case OpCode.getACL:
case OpCode.getChildren:
case OpCode.getChildren2:
case OpCode.ping:
case OpCode.setWatches:
zks.sessionTracker.checkSession(request.sessionId,
request.getOwner());
break;
}
request.zxid = zks.getZxid();
// 转发请求给下一个处理器
nextProcessor.processRequest(request);
}
5.2.2 事务处理器ProposalRequestProcessor(包含SyncRequestProcessor、AckRequestProcessor)
事务处理器接收到请求后,会先将请求发送给提交处理器CommitProcessor,但此时提交处理器CommitProcessor还不会立即处理该请求,提交处理器需要等到ACK处理器真正提交该请求后
,才会处理该请求。将请求封装成事务,发送给其他follower节点
,并同时将请求发送给同步处理器SyncRequestProcessor,源码如下
org.apache.zookeeper.server.ProposalRequestProcessor
public void processRequest(Request request) throws RequestProcessorException {
if(request instanceof LearnerSyncRequest){
zks.getLeader().processSync((LearnerSyncRequest)request);
} else {
// 先将请求发送给提交处理器CommitProcessor,提交处理器会等待该请求的真正提交
nextProcessor.processRequest(request);
if (request.hdr != null) {
try {
// 将请求封装成事务,发送给其他follower节点
zks.getLeader().propose(request);
} catch (XidRolloverException e) {
throw new RequestProcessorException(e.getMessage(), e);
}
// 将请求发送给同步处理器SyncRequestProcessor
syncProcessor.processRequest(request);
}
}
}
同步处理器SyncRequestProcessor会将请求写入到日志文件中,并且记录请求数,如果请求数到达某个值,会生成快照文件(前面章节有介绍快照文件的生成策略)
@Override
public void run() {
while (true) {
Request si = null;
if (toFlush.isEmpty()) {
si = queuedRequests.take();
} else {
si = queuedRequests.poll();
if (si == null) {
flush(toFlush);
continue;
}
}
if (si == requestOfDeath) {
break;
}
if (si != null) {
// 将请求Request写入缓存中
if (zks.getZKDatabase().append(si)) {
logCount++;
if (logCount > (snapCount / 2 + randRoll)) {
// 生成快照文件
}
} else if (toFlush.isEmpty()) {
if (nextProcessor != null) {
// 将请求转发给ACK处理器
nextProcessor.processRequest(si);
}
continue;
}
toFlush.add(si);
// 当缓存的请求数大于1000时,写入到磁盘的日志文件
if (toFlush.size() > 1000) {
flush(toFlush);
}
}
}
}
private void flush(LinkedList<Request> toFlush) {
if (toFlush.isEmpty())
return;
// 将缓存写入到磁盘的日志文件中
zks.getZKDatabase().commit();
while (!toFlush.isEmpty()) {
Request i = toFlush.remove();
if (nextProcessor != null) {
// 将请求转发给ACK处理器
nextProcessor.processRequest(i);
}
}
}
ACK处理器AckRequestProcessor会对接收到的事务进行确认(leader节点),此时还需要等待follower节点对该事务的确认,当超过半数节点确认该事务,leader节点才会真正提交该事务对应的请求Request给提交处理器CommitProcessor。源码如下
synchronized public void processAck(long sid, long zxid, SocketAddress followerAddr) {
if (outstandingProposals.size() == 0) {
return;
}
if (lastCommitted >= zxid) {
return;
}
Proposal p = outstandingProposals.get(zxid);
// 添加确认
p.ackSet.add(sid);
// 如果超过半数节点确认
if (self.getQuorumVerifier().containsQuorum(p.ackSet)){
outstandingProposals.remove(zxid);
// 给其他follower节点发送COMMIT命令
commit(zxid);
// 真正提交请求Request给提交处理器CommitProcessor
zk.commitProcessor.commit(p.request);
}
}
5.2.3 提交处理器CommitProcessor
提交处理器接收到真正提交的请求Request之后,会将请求Request转发给最终处理器FinalRequestProcessor
5.2.4 最终处理器FinalRequestProcessor
最终处理器接收到请求Request之后,会将请求Request的数据更新到内存中,并返回响应信息,源码如下:
synchronized (zks.outstandingChanges) {
if (request.hdr != null) {
TxnHeader hdr = request.hdr;
Record txn = request.txn;
// 将请求数据更新到内存中
rc = zks.processTxn(hdr, txn);
}
if (Request.isQuorum(request.type)) {
// 添加到近期事务列表中
zks.getZKDatabase().addCommittedProposal(request);
}
}
long lastZxid = zks.getZKDatabase().getDataTreeLastProcessedZxid();
ReplyHeader hdr = new ReplyHeader(request.cxid, lastZxid, err.intValue());
try {
// 返回响应信息
cnxn.sendResponse(hdr, rsp, "response");
if (closeSession) {
cnxn.sendCloseSession();
}
} catch (IOException e) {
LOG.error("FIXMSG",e);
}
至此请求的大致处理流程就结束了
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。