源码项目zookeeper-3.6.3:核心工作流程
客户端与服务端链接建立
客户端启动和发起链接
ClientCnxn.java
-SendThread
-run
-while(state.isAlive()) {
//如果还没链接上,则建立链接
if(!clientCnxnSocket.isConnected()) {
//完成链接: NIO 客户端 链接 NIO 服务端
startConnect(serverAddress);
}
//Session已经链接 超时时间处理/链接超时处理
if(state.isConnected()) {
to = readTimeout - clientCnxnSocket.getIdleRecv();
} else {
to = connectTimeout - clientCnxnSocket.getIdleRecv();
}
//Session 超时了
if(to <= 0) {
throw new SessionTimeoutException(warnInfo);
}
//心跳处理
if(state.isConnected()) {
sendPing();
}
//执行读写处理 IO 处理
clientCnxnSocket.doTransport(to,pendingQueue,ClientCnxn.this);
}
-primeConnection
//生成一个 链接请求发送给 服务端
//其实在这之前,已经完成了,只是再发送一个 连接请求给 ZK Server,建立 Session
-ConnectRequest conReq = new ConnectRequest(0, lastZxid, sessionTimeout, sessId, sessionPasswd);
//将链接请求放到 outgoingQueue 队列中,等待发送SendThread 消费该队列,执行队列中的请求对象的发送!
//由 clientCnxnSocket.doTransport() 来处理
outgoingQueue.addFirst(new Packet(null,null,conReq,null,null,readonly));
//注册 OP_READ 和 OP_WRITE 事件
clientCnxnSocket.connectionPrimed();
//通过 startConnect 跟服务端建立链接
-startConnect
//通过 ClientCnxnSocketNIO 发起链接请求
-clientCnxnSocket.connect(addr);
//进入ClientCnxnSocketNIO.java
ClientCnxnSocketNIO.java
-connect
//创建 NIO 客户端
-SocketChannel sock = createSock();
//链接 ZK Server
-registerAndConnect(sock,addr);
-registerAndConnect
//注册 OP_CONNECT 事件 客户端注册: OP_CONNECT 服务端注册: OP_ACCEPT
-sockKey = sock.register(selector, SelectionKey.OP_CONNECT);
//链接 ZK Server 建立物理链接
//发起链接,当返回值 immediateConnect 为 true 的时候,意味着链接建立好了。但是还有一些处理
-boolean immediateConnect = sock.connect(addr);
//建立逻辑链接
//因为异步,不一定马上完成链接,成了链接之后,发送链接请求给 zk server,建立 Session
-if(immediateConnect) {
sendThread.primeConnection();
//进入ClientCnxn.java
}
服务端接收到NIO客户端的链接请求之后,会创建一个NIOServerCnxn对象来专门负责对该Client进行服务。
ClientCnxnSocketNIO.java
-doTransport
-selector.select(waitTimeOut);
-Set<SelectionKey> selected;
//遍历准备就绪的事件,执行处理
-for(SelectionKey k : selected) {
SocketChannel sc = ((SocketChannel)k.channel());
//处理链接请求发送
if((k.readyOps() & SelectionKey.OP_CONNECT)!=0) sendThread.primeConnection();
//处理读写请求
else if((k.readyOps() & (SelectionKey.OP_READ | SelectionKey.OP_WRITE))!=0)
doIO(pendingQueue, outgoingQueue, cnxn);
-doIO
//处理服务端返回回来的消息
-if(sockKey.isReadable())
sendThread.readResponse(incomingBuffer);
//处理要发送的消息
-if(sockKey.isWritable())
Packet p = findSendablePacket(outgoingQueue, cnxn.sendThread.clientTunneledAuthenticationInProgress());
p.createBB();
sock.write(p.bb);
客户端的 ConnectRequest 发送到了服务端,服务端 NIOServerCnxn 的 doIO() 方法来进行处理
NIOServerCnxn.java
-doIO
//处理读
-if(k.isReadable())
readPayload();
//处理写
-if(k.isWritable())
//写数据返回客户端
handleWrite(k);
-readPayload
//读取链接请求
-if(!initialized)
readConnectRequest();
//读取正常请求: 读写请求
-else
readRequest();
-handleWrite
//写出响应给 客户端
-int sent = sock.write(directBuffer);
只要服务端对客户端执行请求的处理,或者返回响应,都是调用这个方法;
NIOServerCnxn.java
-doIO(){
// 客户端给服务端写了数据过来
if(sockKey.isReadable()){}
// 服务端给客户端返回响应
if(sockKey.isWritable()){}
}
只要客户端对服务端执行响应的处理,或者发送请求,都是调用这个方法;
ClientCnxnSocketNIO.java
-doIO(){
// 服务端给客户端返回响应
if(sockKey.isReadable()){}
// 客户端给服务端发送请求
if(sockKey.isWritable()){}
}
ZooKeeper会话创建和管理
NIOServerCnxn.java
//读取链接请求
-readConnectRequest
//处理链接请求
-zkServer.processConnectRequest(this,incomingBuffer);
//进入ZooKeeperServer.java
ZooKeeperServer.java
-processConnectRequest
//第一部分 反序列化恢复得到 ConnectRequest
-BinaryInputArchive bia = BinaryInputArchive.getArchive(new ByteBufferInputStream(incomingBuffer));
-ConnectRequest connReq = new ConnectRequest();
-connReq.deserialize(bia,"connect");
//第二部分 做 readonly, zxid, sessionTimeOut, sessionID 的处理
//第三部分 根据 sessionID 来决定是恢复 session 呢,还是创建 Session
-if(sessionId==0)
long id = createSession(cnxn,passwd,sessionTimeout);
-else
long clientSessionId = connReq.getSessionId();
-createSession
//通过 SessionTracker 创建 SessionID
long sessionId = sessionTracker.createSession(timeout);
//将 NIOServerCnxn 和 Session 一一映射起来
cnxn.setSessionId(sessionId);
//提交创建 Sessoin 的请求
submitRequest(si);
客户端 ClientCnxnSocketNIO 会接收到响应
ClientCnxnSocketNIO.java
//当 SendThread 发送 ConnectRequest 给服务端,服务端完成处理之后,返回 ConnectResponse 给客户端
-doIO
-if(sockKey.isReadable())
else if(!initialized)
//读取链接结果
readConnectResult();
//进入ClientCnxnSocket.java
ClientCnxnSocket.java
-readConnectResult
//从响应对象中,获取服务端返回回来的 SessionID
-this.sessionId = conRsp.getSessionId();
//针对 ConnectResponse 执行处理:
-sendThread.onConnected(conRsp.getTimeOut(),this.sessionId,conRsp.getPasswd(),isRO);
//进入ClientCnxn.java
ClientCnxn.java
-onConnected
//告知 hostProvider 此时连接的那一台 host 是 OK 的
-hostProvider.onConnected();
//该 ClientCnxn 保存 Session ID
-sessionId = _sessionId;
//会话建立好了,则会响应一个监听事件(path:null, event:None, state:SyncConnected)
-eventThread.queueEvent(new WatchedEvent(Watcher.Event.EventType.None,eventState,null));
1、第一步:ZooKeeper 对象内部的 ClientCnxn 内部的 HostProvider 会随机选一个我们提供的地址,然后委托给 ClientCnxnSocket 去建立和 ZooKeeper 服务端之间的 TCP 链接。
2、第二步:SendThread(Client 的网络发送线程)构造出一个 ConnectRequest 请求(代表客户端与服务器创建一个会话)。同时,Zookeeper 客户端还会进一步将请求包装成网络 IO 的 Packet 对象,放入请求发送队列 outgoingQueue 中去等待发送。
3、 ClientCnxnSocket 从 outgoingQueue 中取出 Packet 对象,将其序列化成 ByteBuffer 后,向服务器进行发送。
4、服务端的 SessionTracker 为该会话分配一个 sessionId,并发送响应。
5、Client 收到响应后,此时此刻便明白自己没有初始化,因此会用 readConnectResult 方法来处理请求。
6、ClientCnxnSocket 会对接受到的服务端响应进行反序列化,得到 ConnectResponse 对象,并从中获取到 Zookeeper 服务端分配的会话 SessionId。
7、通知 SendThread,更新 Client 会话参数(比如重要的 connectTimeout ),并更新 Client 状态;另外,通知地址管理器 HostProvider 当前成功链接的服务器地址