从代码上看Zookeeper Client如何建立与保持服务端的连接

来源:互联网 发布:win7公用网络不能修改 编辑:程序博客网 时间:2024/05/29 04:33
从代码上看Zookeeper Client如何建立与保持服务端的连接

鉴于当前开发中众多项目在重构中使用的一些开源框架如dubbo,disconf,kafka等都是以zookeeper作为其中的服务协调者,服务注册中心, 在调试和解决问题中,有必要了解下Zookeeper client如何与server保持连接和会话,以帮忙我们更好的解决和定位问题。

首先来看,zookeeper client核心处理类图:

Zookeeper: 面向用户的API 对象,zk-client及Curator都是对其的封装上层客户端;
ClientCnxn:实际操作执行对象;
EventThread: zookeeper事件处理线程(Daemon线程);
SendThread: zookeeper server连接线程,负责与发送信息给server并从server接受信息(Daemon线程);
outgoingQueue:待发送数据包Packet队列;
pendingQueue:已发送待接受响应处理的数据包Packet队列;
ClientCnxnSocket:负责socket通信,采用NIO,默认使用ClientCnxnSocketNIO, 高版本中可以使用ClientCnxnSocketNetty;
ZKWatchManager: zookeeper连接对象上的Watcher管理对象, 里面有一个默认defaultWatcher,Zookeeper实例化时会赋值;
Watcher:事件监听观察者。

顺便说一下线程的类型:
用户线程 (User Thread)、守护线程 (Daemon Thread)。 
所谓守护 线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。

以dubbo使用ZkClient为例, 看zookeeper client如何与server建立并保持连接,首先要理解对server的连接是两层含义:
  • 客户端与服务器端的TCP连接
  • 在TCP连接的基础上建立session关联

【注】:ZookeeperTransporter为ZkclientZookeeperTransporter(当然也可以是CuratorZookeeperTransporter)

首先来看Zookeeper构造方法,构造方法有三个参数构成:

  • ZooKeeper集群的服务器地址列表(connectString)
该地址是可以填写多个的,以逗号分隔。如"127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183"
  • sessionTimeout
最终会引出三个时间设置:和服务端协商后的sessionTimeout、readTimeout、connectTimeout
服务器端使用协商后的sessionTimeout:即超过该时间后,客户端没有向服务器端发送任何请求(正常情况下客户端会每隔一段时间发送心跳请求,此时服务器端会从新计算客户端的超时时间点的),则服务器端认为session超时,清理数据。此时客户端的ZooKeeper对象就不再起作用了,需要再重新new一个新的对象了。
客户端使用connectTimeout、readTimeout分别用于检测连接超时和读取超时,一旦超时,则该客户端认为该服务器不稳定,就会从新连接下一个服务器地址。
  • Watcher
作为ZooKeeper对象一个默认的Watcher,用于接收一些事件通知。如和服务器连接成功的通知、断开连接的通知、Session过期的通知等。

一旦客户端开始创建Zookeeper对象,客户端Zookeeper状态state设置为CONNECTING,成功连接上服务器后,客户端Zookeeper状态变更为CONNECTED。

建立TCP连接之后,客户端发送ConnectRequest请求,申请建立session关联,此时服务器端会为该客户端分配sessionId和密码,同时开启对该session是否超时的检测。

当在sessionTimeout时间内,即还未超时,此时TCP连接断开,服务器端仍然认为该sessionId处于存活状态。此时,客户端会选择下一个ZooKeeper服务器地址进行TCP连接建立,TCP连接建立完成后,拿着之前的sessionId和密码发送ConnectRequest请求,如果还未到该sessionId的超时时间,则表示自动重连成功,对客户端用户是透明的,一切都在背后默默执行,ZooKeeper对象是有效的。

如果重新建立TCP连接后,已经达到该sessionId的超时时间了(服务器端就会清理与该sessionId相关的数据),则返回给客户端的sessionTimeout时间为0,sessionid为0,密码为空字节数组。客户端接收到该数据后,会判断协商后的sessionTimeout时间是否小于等于0,如果小于等于0,则使用eventThread线程先发出一个KeeperState.Expired事件,通知相应的Watcher,然后结束EventThread线程的循环,开始走向结束。此时ZooKeeper对象就是无效的了,必须要重新new一个新的ZooKeeper对象,分配新的sessionId了。

下面来看EventThread run逻辑:


事件线程EventThread就是从一个事件队列中不断取出事件并进行处理:主要处理三种情况:一种就是我们注册的watch事件,另一种就是处理异步回调函数,还有一种就是死亡。事件由SendThread调用以下方法添加:

再来看SendThread的工作机制:
一句话讲:SendThread实际上就是循环读取Socket连接中的Packet,和循环发送等待队列中的Packet。

SendThread中while循环不断做以下几件事:
任务1:不断检测clientCnxnSocket是否和服务器处于连接状态,如果是未连接状态,则从hostProvider中取出一个服务器地址,使用clientCnxnSocket进行连接;
任务2:检测是否超时:当处于连接状态时,检测是否读超时当处于未连接状态时,检测是否连接超时一旦超时,则抛出SessionTimeoutException。可以看到一旦发生超时异常或者其他异常,都会进行清理,并设置连接状态为未连接,然后发送Disconnected事件。至此又会进入任务1的流程,重试连接。

任务3:不断的发送ping通知,服务器端每接收到ping请求,就会从当前时间重新计算session过期时间,所以当客户端按照一定时间间隔不断的发送ping请求,就能保证客户端的session不会过期(会话迁移激活)。

服务端会话迁移公式:
long expireTime = currentTime + sessionTimeout);
expireTime = (expireTime / expirationInterval + 1) * expirationInterval;
任务4:执行IO操作,即发送请求队列中的请求和读取服务器端的响应数据,以及添加事件。

以发送连接请求和接受连接响应为例:
发送请求过程如上图代码标记:
1)从outgoingQueue取出待发数据包(Packet)
2) 创建bb
3) 将bb写到socket
4)从outgoingQueue删除已发送数据包(Packet)





接受响应处理过程如上图代码标记:
1)首先进行反序列化,得到ConnectResponse对象,我们就可以获取到服务器端给我们客户端分配的sessionId和passwd,以及协商后的sessionTimeOut时间。
2)根据协商后的sessionTimeout时间,重新计算readTimeout和connectTimeout值。然后保留和记录sessionId和passwd。最后通过EventThread发送一个SyncConnected连接成功事件。至此,TCP连接和session初始化请求都完成了,客户端的ZooKeeper对象可以正常使用了。







阅读全文
0 0
原创粉丝点击