会话是Zookeeper中最重要的概念之一,客户端与服务端之间的任何交互操作都与会话相关,其中就包括临时节点的生命周期、客户端请求的顺序执行以及Watcher的通知机制等。Zookeeper的连接与会话就是客户端通过实例化Zookeeper对象来实现客户端与服务器创建并保持TCP连接的过程。

会话状态

在Zookeeper客户端和服务端成功完成连接创建后,就建立了一个会话。Zookeeper会话在整个运行期间内,会在不同的会话状态之间进行切换。这些状态一般分为CONNECTING、CONNECTED、RECONNECTING、RECONNECTED和CLOSE等。
一旦客户端开始创建Zookeeper对象,那么客户端状态就变成CONNECTING,同时客户端开始从服务器地址列表中逐个选取IP地址来尝试进行网络连接,直到成功连接上服务器,然后客户端状态变为CONNECTED
通常情况下,伴随着网络闪断或是其他原因,客户端和服务器之间会出现断开情况,一旦碰到这种情况,Zookeeper客户端会进行自动重连操作。同时客户端的状态会再次变为CONNECTING,直到重新连接上服务器后,客户端状态又会再次转变成CONNECTED。因此通常情况下,在Zookeeper运行期间,客户端的状态总是介于CONNECTINGCONNECTED之间。
如果出现会话超时、权限检查失败、客户端主动退出程序等,客户端的状态会直接变更为CLOSE
会话 - 图1

会话创建

Session

Session是Zookeeper中会话实体,代表了一个客户端会话。其中包含以下4各基本属性。
image.png

sessionID

sessionID用来标识一个唯一的会话,因此Zookeeper必须保证sessionID的全局唯一性。在每次客户端向服务端发起会话创建请求时,服务端都会向其分配一个sessionID。

  1. //org.apache.zookeeper.server.SessionTrackerImpl#initializeNextSessionId
  2. public static long initializeNextSessionId(long id) {
  3. long nextSid;
  4. nextSid = (Time.currentElapsedTime() << 24) >>> 8;
  5. nextSid = nextSid | (id << 56);
  6. if (nextSid == EphemeralType.CONTAINER_EPHEMERAL_OWNER) {
  7. ++nextSid; // this is an unlikely edge case, but check it just in case
  8. }
  9. return nextSid;
  10. }

sessionID生成步骤:

1.获取当前时间的毫秒表示。
2.左移24位。
左移24位是为了防止负数出现。
3.右移8位。
>>> 8,无符号右移8位。
4.添加机器标识:SID。
SID就是在myid文件中配置的机器id。
5.将步骤3和步骤4得到的两个64位表示的数值进行|操作。高8位确定了所在机器,后56位使用当前时间的毫秒进行随机。

SessionTracker

SessionTracker是Zookeeper服务端的会话管理器。负责会话的创建、管理和清理等工作。整个会话的生命周期都离不开SessionTracker的管理。每一个会话在SessionTracker都保留了3份。
1.sessionById,用于根据sessionID来管理session实体。

  1. protected final ConcurrentHashMap<Long, SessionImpl> sessionsById =
  2. new ConcurrentHashMap<Long, SessionImpl>();

2.sessionsWithTimeout,根据sessionID来管理会话的超时时间,该数据结构和Zookeeper的内存数据库相连通,会被定期持久化到快照文件中。

  1. private final ConcurrentMap<Long, Integer> sessionsWithTimeout;

3.sessionSets,用于根据下次会话超时时间点来归档会话,便于进行会话管理和超时检查。

创建连接

服务端对于客户端的会话创建请求处理,分为4步;
1.处理ConnectRequest请求
2.会话创建
3.处理器链路处理
4.会话响应。

在Zookeeper服务器端,将会由NioServerCnxn来负责接收来自客户端的会话创建请求,并反序列化出ConnectRequest请求,然后根据Zookeeper服务端的配置完成会话超时时间的协商。随后,sessionTracker将会为该会话分配一个sessionID,并将其注册到sessionById和sessionsWithTimeout中,同时进行会话的激活。随后,该会话请求在Zookeeper服务端的各个请求处理器之间进行顺序流转,最终完成会话的创建。

会话管理

分桶策略

Zookeeper的会话管理主要由sessionTracker负责的,其采用了一种特殊的会话管理方式,我们称之为分桶策略。分桶策略是指将类似的会话放在同一区块中进行管理,以便Zookeeper对会话进行不同区块的隔离处理以及同一区块的统一处理。

Zookeeper将所有的会话都分配在不同的区块中,分配的原则是每个会话的下次超时时间点ExpirationTime,是指该会话最近一次可能超时的时间点,对于一个新创建的会话而言,其会话创建完毕后,Zookeeper就会计算ExpirationTime,计算方式为:ExpirationTime = CurrentTime + SessionTimeout。
Zookeeper的Leader服务器在运行期间会定时的进行会话超时时间的检查,时间间隔为ExpirationInterval,单位毫秒,默认为tickTime的值,完整的ExpirationTime 计算方式:
ExpirationTime = CurrentTime + SessionTimeout.
ExpirationTime = (ExpirationTime
/ExpirationInterval+1)* ExpirationInterval。

会话激活

为了保持客户端会话的有效性,在Zookeeper的运行过程中,客户端会在会话超时时间过期范围内向服务端发送PING请求来保持会话的有效性,又称心跳检测。同时,服务端需要不断的接收来自客户端的心跳检测,并且需要重新激活对应的客户端会话。这整个激活的过程为TouchSession。会话激活的过程,不仅能够使服务端检测到对应客户端的存活性,同时也能让客户端自己保持连接状态。
1.检查该会话是否已经被关闭。
Leader会检查该会话是否已经被关闭,如果该会话已经被关闭,那么不在继续激活该会话。
2.计算该会话新的超时时间ExpirationTime_New。
如果该会话尚未关闭,那么就开始激活会话。首先要计算出该会话下一次超时时间点。
3.定位该会话当前的区块。
获取该会话老的超时时间,并根据该超时时间来定位到所在的区块。
4.迁移会话
将该会话从老的区块取出,放入到新的超时时间对应的新区块中。

在Zookeeper的设计中会出现两种情况的会话激活:
1.只要客户端向服务端发送请求,包括读或写请求,那么就会触发一次会话激活。
2.如果客户端发现在sessionTimeout/3 时间内尚未和服务器进行任何通信,那么就会主动发起一个PING请求,服务端收到该请求就会触发一次会话激活。

会话超时检查

在Zookeeper中,会话超时检查是由SessionTracker负责的。SessionTracker中有一个单独的线程专门进行会话超时检查,称为超时检查线程。工作机制是:逐个依次的对会话桶中剩下的会话进行清理。
如果一个会话被激活,那么就会从上一个会话桶迁移到下一个会话桶中,所以留在老的会话桶中的会话都是尚未被激活的。超时检查线程的任务就是定时检查出这个会话桶中所有剩下的未被迁移的会话。超时检查线程只要在指定的时间点上进行检查即可,这样提高了会话的检查效率,而且是批量清理,性能非常好。

会话清理

当SessionTracker的会话超时检查线程整理出一些已过期的会话后,就需要进行会话清理。
1.标记会话状态为已关闭。
由于整个会话清理过程需要一段时间,因此为了保证在此期间内不在处理来自该客户端的新请求,就需要将该会话状态标记为已关闭。
2.发起会话关闭请求。
为了使对该会话的关闭操作在整个服务端集群中都生效,Zookeeper使用了提交会话关闭请求的方式,并立即交付给PreRequestProcessor处理器进行处理。
3.收集需要清理的临时节点
在Zookeeper中,一旦某个会话失效后,那么和该会话相关的临时节点都需要被一并清除掉。在Zookeeper内存数据库中,为每个会话都单独保留了一份由该会话维护的所有临时节点集合,因此在会话清理阶段,只需要根据当前会话的sessionID从内存数据库中获取到临时节点列表即可。
4.添加节点删除事务变更
完成该会话相关的临时节点收集后,Zookeeper会逐个将这些临时节点转换成节点删除请求,并放入事务变更队列outstandingChanges中。
5.删除临时节点。
FinalRequestProcessor会触发内存数据库,删除该会话对应的所有临时节点。
6.移除会话
完成节点删除后,需要将会话从SessionTracker中移除。
7.关闭NioServerCnxn
最后,从NioServerCnxnFactory中找到该会话对应的NioServerCnxn,将其关闭。

重连

当客户端和服务端之间的网络连接断开时,Zookeeper客户端会自动进行反复的重连,直到最终成功连接上Zookeeper集群中的一台机器。在这种情况下,再次连上服务端的客户端可能会处于以下两种状态:
1.CONNECTED:如果在会话超时时间内重新连接上了Zookeeper集群中任意一台机器,那么重连成功。
2.EXPIRED:在会话超时时间以外重新连接上,那么服务端其实已经对该会话进行了会话清理操作,因此再次连接上的会话被视为非法会话。

连接断开:CONNECTION_LOSS

有时因为网络闪断导致客户端与服务器断开连接,或是客户端当前连接的服务器出现问题导致连接断开,统称为客户端与服务器连接断开。在这种情况下Zookeeper客户端会自动从地址列表中选出一个新的连接地址并尝试重新连接,直到最终成功连上服务器。
image.png

会话失效:SESSION_EXPIRED

image.png

会话转移:SESSION_MOVED

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