目录
- 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。负责会话的创建、管理、清理等
会话创建流程
- 客户端选择服务器地址发起连接
SendThread线程随机选择一个我们提供的zookeeper服务器地址,委托给ClientCnxnSocket去创建连接。 - 创建TCP连接
ClientCnxnSocket和服务器之间建立一个TCP长连接 - 发送请求创建客户端连接
构造一个 ConnectRequest请求 、并转换为Packet对象,放到outgoingQueue队列中。ClientCnxnSocket将Packet从队列中取出,序列化后发送给服务端。 - 服务器处理请求
服务端为此客户端分配一个SessionId,并发送响应 - 客户端接收响应
ClientCnxnSocket接收服务端的响应,判断当前客户端的状态是否已变化:如果尚未完成初始化,说明这是一个创建会话的请求 - 更新客户端状态
客户端从中读取被分配的SessionId,通知SendThread更新Client会话参数、更新客户端的状态 - 生成事件
为了让上层应用感知到Session创建成功,SendThread会生成一个事件并放入EventThread,EventWatcher收到事件后会查询ZKWatcherManager对应的Watcher,然后放入waiting队列。 - 处理事件
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已经超时后:
- 标记此Session状态为已关闭,这样在关闭期间也不能处理该客户端的请求
- 发起关闭会话的请求,收集需要清理的临时节点。一旦某个会话失效后,与该会话相关的临时节点都需要被清除。
临时节点的生命周期为Session,即在一个连接session有效期内临时节点是活跃的,当连接断开后session自然会过期,那么临时节点就会被删除 - 针对这些节点,创建节点删除事务,从内存数据库中删除
- 从SessionTracer中移除此Session,并关闭此Session对应的NIOServerCnxn
会话激活
客户端与服务端完成连接之后,生成的过期时间并不是一直不变的,而是会随着客户端与服务端的交互更新。也就是说,客户端会通过发送请求或者心跳来保持会话的有效性。
过期时间的更新,就意味着Session需要在桶之间迁移。
- 客户端向服务端发送请求,都会触发一次激活。
- 客户端一直没有请求,在TimeOut的三分之一时间,发送一次Ping,用来续约。
- 客户端直接断开,那么TimeOut时间到期后,当前Session会被服务端扫描到并清理。
会话重连
因为在客户端存在心跳机制不断地进行激活,所以一般情况下会话是一直有效的。但是当客户端与服务端之间的连接断开后,用户在客户端可能主要会看到两类异常:
- CONNECTION_LOSS:连接断开
当客户端与服务端之间网络断开时,客户端将会进行反复尝试重连集群中其他的服务器,直到连接上Zookeeper服务端中的一台机器 - SESSION_EXPIRED:会话过期
客户端和服务器连接断开之后,由于重连期间耗时过长,超出了sessionTimeout还没有成功重连,那么服务器开始进行会话清理。但是客户端此时并不能感知会话已失效。
一直等到客户端重新连接上服务器,服务器会告知客户端该会话已经失效(SESSION_EXPIRED),然后再关闭当前会话。
所以重连之后的客户端可能有两种状态:
- Connected:会话超时时间之内,重连成功
- Expired:重连超出了会话超时时间