会话状态

在ZooKeeper客户端与服务端成功完成连接创建后,就建立了一个会话,在整个运行期间的生命周期 中,会在不同的状态之间进行切换,一般为CONNECTING、CONNECTED、RECONNECTING、RECONNECTED和CLOSE。一旦客户端开始创建ZooKeeper对象,客户端状态就会变成CONNECTING,同时客户端开始从服务器地址列表中逐个选取IP地址来尝试进行网络连接,直至成功连接上服务器,然后将客户端状态变更为CONNECTED。由于网络或其他原因,客户端与服务器之间的连接会出现断开情况,此时,客户端会自动进行重连操作,同时客户端状态再次变为CONNECTING,直到重新连接上服务器,客户端状态再次变为CONNECTED。如果出现会话超时、权限检查失败或是客户端主动退出程序等情况,客户端的状态会直接变为CLOSED。

会话创建

Session是ZooKeeper中的会话实体,代表了一个客户端会话。其包含以下4个基本属性。

属性

说明

sessionID

会话ID。用来唯一标识一个会话,每次客户端创建的时候,ZooKeeper都会为其分配一个全局唯一的sessionID。

timeOut

会话超时时间。客户端在构造ZooKeeper实例的时候,会配置一个sessionTimeout参数用于指定会话超时时间,客户端向服务器发送这个时间后,服务器会根据自己的超时时间限制最终的会话超时时间。

tickTime

下次会话超时时间点。为了高效低耗的实现会话的超时检查与清理,ZooKeeper会为每一个会话标记一个下次会话超时时间点。tickTime是一个13位的long型数据,其值接近于当前时间加上timeOut,但不完全相等。

isClosing

该属性标记一个会话是否已经被关闭,当服务端检测到一个会话已经超时失效的时候,会将会话的isCloseing属性标记为“已关闭”,已确保不再处理来自该会话的新请求。

SessionTracker是Zookeeper服务端的会话管理器,负责会话的创建、管理和清理等工作。每一个会话在SessionTracker内部都保留了三份。

sessionsById

sessionsById是一个HashMap<Long, SessionImpl>类型的数据结构,用于根据sessionID来管理Session实体。

sessionsWithTimeout

sessionsWithTimeout是一个ConcurrentHashMap<Long, Integer>类型的数据结构,用于根据sessionID来管理会话的超时时间。该数据结构和Zookeeper内存数据库相连通,会被定期持久化到快照文件中去。

sessionSets

sessionSets是一个HashMap<Long, SessionSet>类型的数据结构,用于根据下次会话超时时间点来归档会话,便于进行会话管理和超时检查。

服务端对于客户端的“会话创建”请求的处理,大体可分为处理ConnectRequest请求、会话创建、处理器链路处理和会话响应四大步骤。

会话管理

分桶策略

分桶策略是指将类似的会话放在同一区块中进行管理,以便于Zookeeper对会话进行不同区块的隔离处理以及同一区块的统一处理。Zookeeper按照每个会话的“下次超时时间点”(ExpirationTime)的原则,将所有的会话分配在了不同的区块之中。ExpirationTime是指该会话最近一次可能超时的时间点。在Zookeeper的leader服务器运行期间会定时的进行会话超时检查,其时间间隔是ExpirationInterval,单位是毫秒,默认值是tickTime的值,即每2000毫秒进行一次会话超时检查。为了方便多个会话同时进行超时检查,ExpirationTime的计算方式是 (time / expirationInterval + 1) * expirationInterval;time是当前时间的毫秒数。

会话激活

客户端在会话超时时间过期范围内向服务端发送PING请求来保持会话的有效性,同时,服务端需要不断的接收来自客户端的心跳检测,并且需要重新激活对应的客户端会话,这个激活的过程称为TouchSession。会话激活的过程,不仅能使服务端检测到对应客户端的存活性,同时也能让客户端自己保持连接状态。

Leader服务器激活客户端会话流程:

1.检验该会话是否已经被关闭。

2.计算该会话新的超时时间ExpirationTime_New。

3.定位该会话当前的区块。

4.迁移会话。

通过以上4步就基本完成会话激活的过程。

激活时机:在服务端的设计中,只要客户端有请求发送到服务端,就会触发一次会话激活。只要客户端向服务端发送请求,包括读或写请求,就会触发一次会话激活。如果客户端在sessionTimeout/3时间内尚未和服务器进行过任何通信,就主动发起一个PING请求。服务端接收到该请求后,就会触发一次会话激活。

会话超时检查

SessionTracker中有一个单独的线程专门进行会话超时检查,在会话分桶策略中,将ExpirationInterval的整数倍作为时间点来分布会话,因此,超时检查线程只需要在这些指定的时间点上进行检查即可,这样既提高了会话的检查效率,而且由于是批量操作,因此性能非常好。

会话清理

1.标记会话状态为“已关闭”。为了保证会话清理期间不再处理来自该客户端的新请求,SessionTracker会首先将该会话的isClosing属性标记为true。

2.发起“会话关闭”请求。为了使该会话的关闭操作在整个服务端集群中都生效,使用了“会话关闭”请求的方式,并立即交给preRequestProcessor处理器进行处理。

3.收集需要清理的临时节点。在Zookeeper的内存数据库中,为每个会话都单独保存了一份由该会话维护的所有临时节点集合,在会话清理阶段,只需要根据当前即将关闭的会话的sessionID从内存数据库中获取到这份临时节点列表即可。在Zookeeper处理会话关闭请求之前,有以下两类请求到达了服务端并正在处理中:

节点删除请求,删除的目标节点正好是上述临时节点中的一个。

临时节点创建请求,创建的目标节点正好是上述临时节点中的一个。

对于这两类请求,其共同点都是事务处理尚未完成,因此还没有应用到内存数据库中,针对第一类请求,需要将所有这些请求对应的数据节点路径从临时节点列表中移除,以避免重复删除。针对第二类请求,需要将这些请求对应的数据节点路径添加到临时节点列表中去,以删除这些即将被创建,但是尚未保存到内存数据库中的临时节点。

4.添加“节点删除”事务变更。完成该会话相关的临时节点收集后,ZooKeeeper会逐个将这些临时节点转换成“节点删除”请求,并放入事务变更队列outStandingChanges中去。

5.删除临时节点。FinalRequestProcessor处理器会触发内存数据库,删除该会话对应的所有临时节点。

6.移除会话。完成节点删除后,需要将会话从SessionTracker中移除。

7.关闭NIOServerCnxn。从NIOServerCnxnFactory中找到该会话对应的NIOServerCnxn,将其关闭。

重连

重新连接服务端的客户端可能会处于以下两种状态之一。

CONNECTED:会话超时时间内重新连接上Zookeeeper集群中任一机器,被视为重连成功。

EXPIRED:会话超时时间外重新上,服务端已经对该会话进行了会话清理操作,因此被视为非法会话。

当客户端与服务端之间的连接断开口后,在客户端可能主要看到两类异常:CONNECTION_LOSS和SESSION_EXPIRED。

连接断开:CONNECTION_LOSS。当客户端与服务端连接断开,Zookeeeper客户端会自动从地址列表中重新逐个选取新的地址并尝试重新连接,直至最终成功连接上服务器。

会话失效:SESSION_EXPIRED。SESSION_EXPIRED是指会话过期,通常发生在CONNECTION_LOSS期间。客户端和服务器断开连接之后,由于重连期间消耗时间过长,超过了会话超时时间限制后还没有成功连接上服务器,那么服务器就认为这个会话已经结束了,就会开始会话清理。但客户端本身不知道会话已经失效,并且客户端状态还是DISCONNECTED。之后,如果客户端重新连接上了服务器就会被告知会话已经失效。

会话转移:SESSION_MOVED。会话转移是指客户端会话从一台服务器机器转移到了另一台服务器机器上。