前情提要
上一篇【Zookeeper 源码解读系列, 单机模式(三)】我们讲了单机模式下服务器的启动,配置参数的解析,ZooKeeperServerMain的启动,服务端启动的步骤,启动时加载数据,最后是ZookeeperServer的启动。由于篇幅太长,考虑到大家阅读起来比较困难,就把单机模式下最重要也是最不好理解的一篇拆分出来,单独做为一篇以降低读者阅读时的疲劳感。那么我们就接着上一次的话题继续讲ZookeeperServer启动结束以后,又做了什么事情。本篇也会被收录到【Zookeeper 源码解读系列目录】中。
请求处理器链的概念
现在我们先把代码放一边,先总结下到目前为止服务器都做了什么。刚才我们说到服务端启动的时候,在NIOServerCxnFactory.startup()的方法里启动了一个线程,就是NIOServerCxnFactory这个NIO的线程,这是第一件事情;第二件事情就是加载数据;然后又做了第三件事情,在ZookeeperServer.startup()方法里启动了一个session的跟踪器,这个也是一个线程,我们刚刚说国;最后就是setupRequestProcessors(),这里看名字是在设置请求处理器(RequestProcessors)。顾名思义就是要处理请求的,这里其实一个非常重要的点,为什么说很重要呢?
因为这几乎就是Zookeeper处理事务的核心逻辑,在讲解代码之前,需要先从概念上理清楚逻辑,否则直接看代码真的是一场噩梦。我们先进入setupRequestProcessors();看看这里又写了什么事情:
protected void setupRequestProcessors() {
RequestProcessor finalProcessor = new FinalRequestProcessor(this);
RequestProcessor syncProcessor = new SyncRequestProcessor(this, finalProcessor);
((SyncRequestProcessor)syncProcessor).start();
firstProcessor = new PrepRequestProcessor(this, syncProcessor);
//这里启动的是PrepRequestProcessor首个处理器
((PrepRequestProcessor)firstProcessor).start();
}
进去以后看到这里面有三个处理器Processor的类而且,全都new了出来,这些是什么呢?那么就要重点介绍一下ZooKeeper的请求处理器(RequestProcessors)的逻辑,这个逻辑和我们一般意义上的链表很像,所以权且把这个逻辑叫做处理器链。每当一个请求过来的时候,就会通过不同的请求处理器去处理不同的逻辑。在单机模式下有三种处理器(也是三个线程类):
PrepRequestProcessor、SyncRequestProcessor、FinalRequestProcessor,这些处理器分开来说,就是做下面这些任务的:
PrepRequestProcessor->做认证,生成txn事务对象,完成后转交给SyncRP
SyncRequestProcessor->事务对象持久化(生成事务文件),打快照,完成后转交给FinalRP
FinalRequestProcessor->做内存更改,处理事件响应啥的都是在这里弄的,返回客户端响应
所以整条链就是这样的:
PrepRequestProcessor.next=SyncRequestProcessor.next=FinalRequestProcessor
为什么可以这样写呢?因为这些处理器里面都有一个next的属性,指向下一下处理器。此处要注意记住一点:第一个请求firstProcessor是NIOServerCnxn中被调用的,也就是说处理器链的实例化以及启动都是在服务端。
介绍完什么是处理器链的概念,我们现在说到这里,其实遗留了两大问题:
NIOServerCxnFactory这个线程做了什么事情?- 我们根据NIO的思想从
socket-channel取出数据以后,谁去做了处理?
根据我们之前的分析,NIO是处理Socket连接的,所以问题1取数据应当是NIOServerCxnFactory做的。而问题2呢,当然应该是我们的处理器链做的。那么后面我们看下代码里面是不是和我们的猜想一样。
NIOServerCnxnFactory线程
如刚才所说先去看run()方法,其实从道理上讲,NIOServerCnxnFactory的逻辑应该和我们客户端NIOClientCnxn的逻辑应该是镜像相反的。所以这里面也应该有读和写,那让我们看下代码:
public void run() {
while (!ss.socket().isClosed()) {
try {
/**构建选择器,从channel里面拿出数据,封装为selectedList,略过**/
for (SelectionKey k : selectedList) {
//如果取到的是一个connection的请求,则建立连接
if ((k.readyOps() & SelectionKey.OP_ACCEPT) != 0) {
SocketChannel sc = ((ServerSocketChannel) k .channel()).accept();
InetAddress ia = sc.socket().getInetAddress();
int cnxncount = getClientCnxnCount(ia);
if (maxClientCnxns > 0 && cnxncount >= maxClientCnxns){
LOG.warn("Too many connections from " + ia + " - max is " + maxClientCnxns );
sc.close();
} else {
LOG.info("Accepted socket connection from "
+ sc.socket().getRemoteSocketAddress());
sc.configureBlocking(false);
SelectionKey sk = sc.register(selector, SelectionKey.OP_READ);
NIOServerCnxn cnxn = createConnection(sc, sk);//建立连接
sk.attach(cnxn);
addCnxn(cnxn);
}
} else if ((k.readyOps() & (SelectionKey.OP_READ | SelectionKey.OP_WRITE)) != 0) {
//如果不是建立连接的,就是一些写或者读的数据
NIOServerCnxn c = (NIOServerCnxn) k.attachment();
c.doIO(k);//和客户端一样开始doIO
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Unexpected ops in select " + k.readyOps());
}
}
}
selected.clear();
} catch (***Exception e) {
/**异常略**/
}
}
closeAll();
LOG.info("NIOServerCnxn factory exited run method");
}
我们略过选择器的部分,到for (SelectionKey k : selectedList)循环这里,碰见了第一个判断条件if ((k.readyOps() & SelectionKey.OP_ACCEPT) != 0),这里的连接是和channel连接,所以是OP_ACCEPT字段,所以说取到的是一个Connection的请求,那么就会走进去,做一些绑定地址,校验参数之类的工作,然后建立连接NIOServerCnxn cnxn = createConnection(sc, sk);,这里没什么好说的。我们走到else if ((k.readyOps() & (SelectionKey.OP_READ | SelectionKey.OP_WRITE)) != 0) 里面,如果不是建立连接的,就是一些写或者读的数据,当然也会间歇性的收到客户端的ping。那么我们是不是找到了一个很熟悉的方法c.doIO(k);呢,所以我们就着重去里面看:
void doIO(SelectionKey k) throws InterruptedException {
try {
/**验证socket没有打开,return;,略过**/
if (k.isReadable()) {
//读取准备完毕
int rc = sock.read(incomingBuffer);
if (rc < 0) {
//剩下的数据小于0,报错
/**抛出EndOfStreamException数据异常**/
}
if (incomingBuffer.remaining() == 0) {
boolean isPayload;
if (incomingBuffer == lenBuffer) {
incomingBuffer.flip();
isPayload = readLength(k);
incomingBuffer.clear();
} else {
isPayload = true;
}
if (isPayload) {
readPayload();//正式加载数据
}
else {
return; }
}
}
if (k.isWritable()) {
/**写就绪,略过**/
}
} catch (***Exception e) {
/**各种异常的逻辑,略过**/
}
}
我们进入以后看到如果k.isReadable()都就绪了,那么就读取客户端发来的数据,这里没什么可说的,如果一切都做好了那么就开始readPayload();正式加载数据了:
private void readPayload() throws IOException, InterruptedException {
if (incomingBuffer.remaining() != 0) {
//是否还有遗留的数据
int rc = sock.read(incomingBuffer);//接着读剩下的数据
if (rc < 0) {
//剩下的数据小于0,报错
/**抛出EndOfStreamException数据异常**/
}
}
if (incomingBuffer.remaining() == 0) {
//如果全部读完了
packetReceived();//计数的方法
incomingBuffer.flip();
if (!initialized) {
//初始化Y/N
readConnectRequest();//N:重新连接
} else {
readRequest(); //Y:开始读取请求
}
lenBuffer.clear();
incomingBuffer = lenBuffer;
}
}
进入以后又开始判断是不是还有遗留的数据if (incomingBuffer.remaining() != 0) ,如果有那就继续把剩余的sock.read(incomingBuffer)数据都读出来,如果if (incomingBuffer.remaining() == 0)如果全部读完了,就可以请求了,packetReceived();这是个计数器没什么用略过,继续判断if (!initialized)有没有初始化成功,如果没有重新连接readConnectRequest();,如果已经就绪开始读取请求readRequest();,所以我们还得去readRequest();里面看内容:
private void readRequest() throws IOException {
zkServer.processPacket(this, incomingBuffer);
}
里面只有一句话,那我们还得接着往里面走,但是要说明一点:到了这里服务器实例已经开始处理收到的packet了,incomingBuffer就是socket里面的数据,下面进入processPacket(this, incomingBuffer);
public void processPacket(ServerCnxn cnxn, ByteBuffer incomingBuffer) throws IOException {
InputStream bais = new ByteBufferInputStream(incomingBuffer);
BinaryInputArchive bia = BinaryInputArchive.getArchive(bais);
RequestHeader h = new RequestHeader();//构造请求头
h.deserialize(bia, "header");
incomingBuffer = incomingBuffer.slice();
//根据header的不同类型进行处理
if (h.getType() == OpCode.auth) {
//这个auth其实就是addauth命令
/**这里我们先不看,后面ACL会仔细讲解**/
} else {
//不是auth
if (h.

本文深入解析ZooKeeper单机模式下服务器的启动流程,包括配置参数解析、服务端启动步骤、数据加载及ZookeeperServer启动。重点介绍了请求处理器链的概念与运作逻辑,从PrepRequestProcessor到SyncRequestProcessor再到FinalRequestProcessor,详细分析了各处理器如何处理请求、事务持久化及内存更新过程。
最低0.47元/天 解锁文章
335

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



