初始化阶段
private final ZKWatchManager watchManager = new ZKWatchManager(); //2(直接被默认初始话的)
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,
boolean canBeReadOnly)
throws IOException
{
//1
watchManager.defaultWatcher = watcher; //1
ConnectStringParser connectStringParser = new ConnectStringParser(
connectString);
HostProvider hostProvider = new StaticHostProvider(
connectStringParser.getServerAddresses()); //3
cnxn = new ClientCnxn(connectStringParser.getChrootPath(),
hostProvider, sessionTimeout, this, watchManager,
getClientCnxnSocket(), canBeReadOnly); //4
cnxn.start(); //6
}
//pendingQueue/outgoingQueue时在ClientCnxn类中直接被初始化的(默认初始化)
**private final LinkedList<Packet> pendingQueue = new LinkedList<Packet>();
** private final LinkedList<Packet> outgoingQueue = new LinkedList<Packet>();
- 初始化ZooKeeper对象
通过调用ZooKeeper的构造方法来实例化一个ZooKeeper对象,在初始化过程中,会创建一个客户端的Watcher管理器:ClientWatchManager
private final ZKWatchManager watchManager=new ZKWatchManager (); - 设置会话的默认Watcher
如果在构造方法中传入一个Watcher对象,则ZooKeeper将默认的watcher设置为该Watcher对象 - 构造ZooKeeper服务器地址列表管理器: HostProvider
在ZooKeeper的构造函数中,客户端会将器存放在服务器地址列表管理器HostProvider中 - 创建并初始化客户端网络连接器: ClientCnxn
ClientCnxn用来管理客户端与服务器的网络交互。另外在创建ClientCnxn的同时,还会初始化客户端的两个核心队列outgoingQueue和pendingQueue;分别作为客户端请求发送队列和服务端响应的等待队列 - 初始化SendThread和EventThread
SendThread用于管理客户端和服务端之间的所有网络I/O,后者则用于处理客户端的事件(当收到服务器响应时,产生的序列)
会话创建阶段
- 启动SendThread和EventThread
SendThread搜寻会判断当前客户端的状态,进行一系列清理性工作,为客户端发送“会话创建”请求做准备
- 获取一个服务器的地址(这个应该是在请求之前获取的)
SendThread首先需要获取一个ZooKeeper服务器的目标地址,通常是从HostProvider中随机获取出一个地址(根据源码分析,是依次获取的),然后与ZooKeeper服务器之间的TCP连接
public InetSocketAddress next(long spinDelay) {
currentIndex = ++currentIndex % serverAddresses.size();//可以看到是依次获取的服务端地址。进行请求的
.......
InetSocketAddress curAddr = serverAddresses.get(currentIndex);
.......
}
- 创建一个TCP连接
获取到一个服务器地址后,ClientCnxnSocket负责和服务器创建一个TCP长连接
startConnect(serverAddress); //建立连接
private void startConnect(InetSocketAddress addr) throws IOException {
.......
clientCnxnSocket.connect(addr); //这个应该是最重要的
}
@Override
void connect(InetSocketAddress addr) throws IOException {
SocketChannel sock = createSock();
try {
registerAndConnect(sock, addr); //在NIO中注册了该socket
}
}
void registerAndConnect(SocketChannel sock, InetSocketAddress addr)
{
sockKey = sock.register(selector, SelectionKey.OP_CONNECT);
boolean immediateConnect = sock.connect(addr);
if (immediateConnect) {
sendThread.primeConnection(); //这个最关键了 ste 9
}
}
void primeConnection() {
isFirstConnect = false;
long sessId = (seenRwServerBefore) ? sessionId : 0;
ConnectRequest conReq = new ConnectRequest(0, lastZxid,
sessionTimeout, sessId, sessionPasswd);
......
// SendThread会根据当前客户端的实际设置,构造一个ConnectReques请求,即**ConnectRequest
}
-
构造ConnectedRequest请求
在TCP连接创建完毕后,可能会有人认为(就是我),这样是否说明已经和ZooKeeper服务器完成连接了呢? 其实不然(打脸),步骤8只是纯粹地从网络TCP层面完成了客户端与服务端之间的socket连接,但未完成ZooKeeper客户端的会话创建.(需要知道传输层上方存储一个会话层) -
发送请求
当客户端请求准备完毕后,就可以开始向服务端发送请求了。ClientCnxnSocket负责从outgoingQueue中取出一个待发送的Packet对象,并将其序列化为ByteBuffer,向服务端进行发送。
响应处理阶段
- 接收服务端响应
ClientCnxnSocket接收到服务端的响应后,会首先判断当前的客户端状态时否“已初始化”,如果尚未初始化,那么就认为该响应一定时会话创建请求的响应,直接交由readConnectResult方法来处理该响应
// 这是在SendThread中的run代码中的一句// this code 是在一个while(connect.isAlive)的循环中
clientCnxnSocket.doTransport(to, pendingQueue, outgoingQueue, ClientCnxn.this);
void doTransport(int waitTimeOut, List<Packet> pendingQueue, LinkedList<Packet> outgoingQueue, ClientCnxn cnxn)
throws IOException, InterruptedException {
selector.select(waitTimeOut);
Set<SelectionKey> selected;
synchronized (this) {
selected = selector.selectedKeys(); //典型的epoll() 多路复用模式
}
下面这段话表明:在下面的代码中都是非阻塞的
// Everything below and until we get back to the select is
// non blocking, so time is effectively a constant. That is
// Why we just have to do this once, here
updateNow();
for (SelectionKey k : selected) { //选择事件完成的;IO完成的,服务响应收到的
SocketChannel sc = ((SocketChannel) k.channel());
if ((k.readyOps() & SelectionKey.OP_CONNECT) != 0) {
if (sc.finishConnect()) { //这里是不是重连的意思
updateLastSendAndHeard();
sendThread.primeConnection();
}
} else if ((k.readyOps() & (SelectionKey.OP_READ | SelectionKey.OP_WRITE)) != 0) {
doIO(pendingQueue, outgoingQueue, cnxn); //这句话是重点;当selector的状态是read or write -> 进行IO读取 //在doIO中会发现 step11中介绍的readConnectResult方法
}
}
if (sendThread.getZkState().isConnected()) {
synchronized(outgoingQueue) {
if (findSendablePacket(outgoingQueue,
cnxn.sendThread.clientTunneledAuthenticationInProgress()) != null) {
enableWrite();
}
}
}
selected.clear();
}
- 处理Response
在ReadConnectResult中会对接收到的服务端响应进行反序列化操作,从而得到ConnectResponse对象,并从中获取sessionID等信息
void readConnectResult() throws IOException {
ByteBufferInputStream bbis = new ByteBufferInputStream(incomingBuffer);
BinaryInputArchive bbia = BinaryInputArchive.getArchive(bbis);
ConnectResponse conRsp = new ConnectResponse();
conRsp.deserialize(bbia, "connect"); //反序列话,生成ConnectResponse对象
........
this.sessionId = conRsp.getSessionId(); //step12 获得sessionID
sendThread.onConnected(conRsp.getTimeOut(), this.sessionId,
conRsp.getPasswd(), isRO);
}
- 连接成功
连接成功后,一方面需要通知SendThread线程,进一步对客户端进行会话参数的更新;另一方面,需要通知地址管理器HostProvider当前成功连接的服务器地址
void onConnected(int _negotiatedSessionTimeout, long _sessionId,
byte[] _sessionPasswd, boolean isRO) throws IOException {
...... //可以看到下面都在更新SendThread中的参数
readTimeout = negotiatedSessionTimeout * 2 / 3;
connectTimeout = negotiatedSessionTimeout / hostProvider.size();
hostProvider.onConnected(); //通知HostProvider当前成功连接的服务器地址
sessionId = _sessionId;
sessionPasswd = _sessionPasswd;
state = (isRO) ?
States.CONNECTEDREADONLY : States.CONNECTED;
seenRwServerBefore |= !isRO;
KeeperState eventState = (isRO) ?
KeeperState.ConnectedReadOnly : KeeperState.SyncConnected;
eventThread.queueEvent(new WatchedEvent(
Watcher.Event.EventType.None,
eventState, null)); //可以看到这里就是step15中所提到的事件
}
- 生成事件: SyncConnected-None
为了能够让上层应用感知到会话的成功创建,SendThread会生成一个事件SyncConnected-None,代表客户端与服务器会话创建成功,并传递给EventThread线程 - 查询Watcher
EventThread线程收到事件后,会从ClientWatchManager管理器中查询对应的Watcher-》这里就有疑惑了 如何查找呢(首先服务端发送到客户端时通过IP地址的,那么当服务端发送到客户端是,其实客户端所注册的Watcher都可以查找到的。并且不会存在其他客户端的干扰
void readResponse(ByteBuffer incomingBuffer) throws IOException //SendThread中的函数目的是读取服务器的响应,其中有下面这一行代码
WatchedEvent we = new WatchedEvent(event); //we为响应的事件
eventThread.queueEvent( we ); //将we事件加入到EventThread中
public void queueEvent(WatchedEvent event) {
......
WatcherSetEventPair pair = new WatcherSetEventPair(
watcher.materialize(event.getState(), event.getType(),
event.getPath()),
event); //可以看到这里有一个materialize函数 ,其中watcher就是
......
waitingEvents.add(pair);
}
上面的materialize被ZooKeeper实现,其中watcher就是ClientWatchManager
该方法就是先了查找出对应的Wactcher,并进行回调
- 处理事件
EventThread不断地从waitingEvents队列中取出待处理的Watcher对象,然后直接调用该对象的process接口方法,已达到Watcher的目的
总结: ZooKeeper客户端完整的一次会话就完成了。!!!!