1 基础概念
ClientCnxn是网络连接器,管理客户端与服务端的网络交互。
ClientWatchManager保存客户端的watcher
HostProvider服务器地址列表管理器
outgoingQueue客户端的请求发送队列
pendingQueue客户端的服务端响应等待队列
SendThread管理客户端与服务端的所有网络IO
客户端的SendThread通过一定频率向服务端发送PING包实现心跳检测,维护客户端与服务端的会话生命周期;
会话周期内客户端与服务端之间出现TCP连接断开,会自动重连,这个过程是透明的;
SendThread管理客户端所有的请求发送与响应接收;
SendThread将服务端事件传递给EventThread去处理;
EventThread处理客户端的事件,触发客户端注册的Watcher监听。
2 客户端初始化与启动过程
- 设置默认watcher
- 设置Zookeeper服务器地址列表
- 创建ClientCnxn
创建Zookeeper构造方法中传入watcher对象,则此watcher对象会被保存入defaultWatcher中,作为客户端会话期间默认的watcher。
2.1 会话创建的初始化阶段
1 初始化Zookeeper对象,创建的客户端Watcher管理器ClientWatcherManager
2 设置会话默认Watcher,创建Zookeeper对象是传入的Watcher对象会被保存入ClientWatcherManager
3 构造Zookeeper服务器地址列表管理器HostProvider
4 创建并初始化客户端网络连接起ClientCnxn,管理客户端与服务端的网络交互;
同时客户端会初始化队列outgoingQueue用于客户端的请求发送队列和pendingQueue用于服务端响应的等待队列;
同时客户端还要创建ClientCnxnSocket,作为底层IO处理器
5 初始化网络线程SendThread管理客户端与服务端的网络连接,使用ClientCnxnSocket作为底层IO处理器
初始化网络线程EventThread处理客户端的事件,初始化待处理事件队列waitingEvents,存放所有等待被客户端处理的事件。
2.2 会话创建阶段
6 启动SendThread,EventThread
SendThread首先判断客户端的状态,进行一些清理工作,为客户端发送”会话创建“请求做准备。
7 获取一个服务器地址列表
SendThread首先从HostProvider随机获取一个Zookeeper地址,交给ClientCnxnSocket去创建与Zookeeper服务器之间的TCP连接。
8 创建TCP连接
获取到一个服务器地址,ClientCnxnSocket负责与服务器创建一个TCP长连接
9 构造connectRequest请求
SendThread负责构造ConnectRequest,该请求代表客户端要与服务端连接创建会话。ZK客户端还会将该请求包装成网络层的Packet对象,放入请求发送队列outgoingQueue中。
10 发送请求
ClientCnxnSocket从outgoingQueue取出一个代发送的Packet对象,序列化成ByteBuffer对象向服务端发送
2.3 响应处理阶段
11 接收服务端响应
ClientCnxnSocket接到服务端响应,首先判断当前客户端状态是否是“已初始化”,如果未初始化就认为此响应是会话创建请求的响应,直接由readConnectResult方法处理该响应。
12 处理Response
ClientCnxnSocket对服务端响应反序列化,得到ConnectResponse对象,并获取ZK服务端分配的会话SessionId。
13 连接成功
连接成功后,同志SendThread线程进一步设置readTimeout,connectTimeout等参数,并更新客户端状态;
通知地址管理器HostProvider当前成功连接的服务器地址。
14 生成事件SyncConnected-None
SendThread会生成事件,目的是上层应用感知会话创建成功。代表客户端与服务端的会话创建成功,并将该事件传递给EventThread线程。
15 获取Watcher
EventThread收到事件后从ClientWatcherManager中获取对应的watcher,针对SyncConnected-None直接找出步骤2中存储的默认watcher,放入EventThread中的waitingEvents队列中。
16 处理事件
EventThread从waitingEvents队列中获取待处理的watcher对象,调用该对象的process方法。
2.4 服务器地址列表
创建ZK客户端时传入的服务器地址列表时如何生效的?按照顺序连接各个服务器还是随机连接?
服务器地址列表解析器ConenctStringParser将传入的地址列表解析成chrootPath,并且保存下服务器地址列表
- chroot指的是每个客户端为自己设置命名空间,之后该客户端的操作都会限定在自己的命名空间内。例如“192.168.1.1:2181, 192.168.1.2:2181/apps/a”
- ConenctStringParser将IP以及port封装到InetSocketAddress对象,以ArrayList形式保存入ConenctStringParser.serverAddresses属性中,经过处理的地址列表会被进一步封装到StaticHostProvider(提供服务器地址个数,地址列表,回调方法用于客户端与服务端创建连接成功)。
StaticHostProvider实现了接口HostProvider
- 解析地址与端口,封装成集合
- 调用Collections.shuffle将地址列表打散,组装成环形循环队列,使用过程中从这个队列获取地址信息
2.5 网络IO
1 请求发送
outgoingQueue队列中提取出一个可以发送的Packet对象,同时生成客户端请求序号XID,并将其设置到Packet请求头中,然后序列化并发送。
请求发送完毕,立即将Packet保存到pendingQueue中,等待服务端响应后进行相应处理。
2 响应接收
客户端接到服务端响应后,根据客户端请求类型不同,进行不同的处理。
客户端还未初始化,说明客户端与服务端正在创建会话,直接将接到的ByteBuffer(incomingBuffer)序列化成ConnectResponse对象。
客户端处于正常的会话周期,并且接到的服务端响应是一个事件,客户端将接到的ByteBuffer(incomingBuffer)序列化成WatcherEvent对象,并将该对象放入待处理队列。
如果是一个常规请求(Create,GetData,Exists等操作),从pendingQueue队列中取出一个Packet对象进行处理。客户端检查服务端响应中包含的XID确保请求处理顺序,然后将接到的ByteBuffer(incomingBuffer)序列化成Response对象。
finishPacket方法中处理Watcher注册等逻辑。