目录

  • Session结构
  • 会话创建
  • 会话管理
  • 会话清理
  • 会话激活
  • 会话重连


Session结构

Zookeeper在执行任何请求之前,都需要在客户端和服务端之间先建立Session,如客户端的请求顺序执行、watcher的通知机制等。所谓Session,就是客户端与服务端之间创建的一个 TCP长链接

Session结构

  • SessionId:会话id
  • TimeOut:会话从新建到关闭的时长,由Zookeeper服务端管理
  • ExpirationTime:创建时间+TimeOut。是一个绝对时间。
  • IsClosing:会话的关闭状态

Session状态

  • Connecting:正在连接,客户端与服务端建立连接时的状态
  • Connected:已连接,客户端与服务端完成连接
  • ReConnecting:正在重连,客户端与服务端断开连接,Session重连机制发起重连时重新连接的状态
  • ReConnected:已经重连
  • Close:连接关闭

成员变量

// sessionId与Session的映射
protected final ConcurrentHashMap<Long, SessionImpl> 
        sessionsById = new ConcurrentHashMap<Long, SessionImpl>();
//过期策略
private final ExpiryQueue<SessionImpl> sessionExpiryQueue;
//标志session的超时时间
private final ConcurrentMap<Long, Integer> sessionsWithTimeout;

会话创建

首先关注客户端与服务端相关的线程及队列:

客户端

  • 网络连接器:ClientCnxn
  • 关键队列
  • outgoingQueue:客户端请求发送队列
  • pendingQueue:客户端等待服务端响应的等待队列
  • 关键线程
  • SendThread:管理客户端与服务端所有的网络IO
  • ClientCnxnSocket:底层IO处理器
  • EventThread:用于客户端事件处理
  • 队列:waitingEvents,待处理事件队列

服务端

  • 会话管理器:SessionTracker。负责会话的创建、管理、清理等

会话创建流程

  1. 客户端选择服务器地址发起连接
    SendThread线程随机选择一个我们提供的zookeeper服务器地址,委托给ClientCnxnSocket去创建连接。
  2. 创建TCP连接
    ClientCnxnSocket和服务器之间建立一个TCP长连接
  3. 发送请求创建客户端连接
    构造一个 ConnectRequest请求 、并转换为Packet对象,放到outgoingQueue队列中。ClientCnxnSocket将Packet从队列中取出,序列化后发送给服务端。
  4. 服务器处理请求
    服务端为此客户端分配一个SessionId,并发送响应
  5. 客户端接收响应
    ClientCnxnSocket接收服务端的响应,判断当前客户端的状态是否已变化:如果尚未完成初始化,说明这是一个创建会话的请求
  6. 更新客户端状态
    客户端从中读取被分配的SessionId,通知SendThread更新Client会话参数、更新客户端的状态
  7. 生成事件
    为了让上层应用感知到Session创建成功,SendThread会生成一个事件并放入EventThread,EventWatcher收到事件后会查询ZKWatcherManager对应的Watcher,然后放入waiting队列。
  8. 处理事件
    EventWatcher从waiting中取出watcher对象,分别调用其process方法。

分配SessionId:

public static long initializeNextSessionId(long id) {
    long nextSid;
    nextSid = (Time.currentElapsedTime() << 24) >>> 8;
    nextSid = nextSid | (id << 56);
    if (nextSid == EphemeralType.CONTAINER_EPHEMERAL_OWNER) {
        ++nextSid;  // this is an unlikely edge case, but check it just in case
    }
    return nextSid;
}

前7位确定了所在的机器,后57位使用当前时间的毫秒表示进行随机。

会话管理

会话清理

  • 分桶策略

将类似的会话放在同一区块中进行管理,而Zookeeper区分这些Session的维度,就是ExpirationTime。

如此区分的原因:Zookeeper服务端在运行时会对会话进行超时检查,这样区分可以避免对全部的Session进行扫描。

ExpirationTime 的计算方式:

// 1. 先计算当前Session过期的具体时间
ExpirationTime = CurrentTime + SessionTimeout;
// 2. ExpirationInterval:Zk的检查频率(默认2s)。
// 将ExpirationTime映射为ExpirationInterval的倍数。
ExpirationTime = (ExpirationTime / ExpirationInterval + 1) * ExpirationInterval

服务端检查到一个Session已经超时后:

  1. 标记此Session状态为已关闭,这样在关闭期间也不能处理该客户端的请求
  2. 发起关闭会话的请求,收集需要清理的临时节点。一旦某个会话失效后,与该会话相关的临时节点都需要被清除。
    临时节点的生命周期为Session,即在一个连接session有效期内临时节点是活跃的,当连接断开后session自然会过期,那么临时节点就会被删除
  3. 针对这些节点,创建节点删除事务,从内存数据库中删除
  4. 从SessionTracer中移除此Session,并关闭此Session对应的NIOServerCnxn

会话激活

客户端与服务端完成连接之后,生成的过期时间并不是一直不变的,而是会随着客户端与服务端的交互更新。也就是说,客户端会通过发送请求或者心跳来保持会话的有效性。

过期时间的更新,就意味着Session需要在桶之间迁移。

  1. 客户端向服务端发送请求,都会触发一次激活。
  2. 客户端一直没有请求,在TimeOut的三分之一时间,发送一次Ping,用来续约。
  3. 客户端直接断开,那么TimeOut时间到期后,当前Session会被服务端扫描到并清理。

会话重连

因为在客户端存在心跳机制不断地进行激活,所以一般情况下会话是一直有效的。但是当客户端与服务端之间的连接断开后,用户在客户端可能主要会看到两类异常:

  • CONNECTION_LOSS:连接断开
    当客户端与服务端之间网络断开时,客户端将会进行反复尝试重连集群中其他的服务器,直到连接上Zookeeper服务端中的一台机器
  • SESSION_EXPIRED:会话过期
    客户端和服务器连接断开之后,由于重连期间耗时过长,超出了sessionTimeout还没有成功重连,那么服务器开始进行会话清理。但是客户端此时并不能感知会话已失效。
    一直等到客户端重新连接上服务器,服务器会告知客户端该会话已经失效(SESSION_EXPIRED),然后再关闭当前会话。

所以重连之后的客户端可能有两种状态:

  • Connected:会话超时时间之内,重连成功
  • Expired:重连超出了会话超时时间