写在前面
Session、SessionImpl
SessionTracker、SessionTrackerImpl
会话激活
会话分桶
会话超时检查
会话清理
sessionID:会话 ID,用来唯一标识一个会话,每次客户端创建新会话的时候,ZooKeeper都会为其分配一个全局唯一的 sessionID。
TimeOut:会话超时时间。客户端在构造 ZooKeeper 实例的时候,会配置一个sessionTimeout参数用于指定会话的超时时间。ZooKeeper客户端向服务器发送这个超时时间后,服务器会根据自己的超时时间限制最终确定会话的超时时间。
isClosing:该属性用于标记一个会话是否已经被关闭。通常当服务端检测到一个会话已经超时失效的时候,会将该会话的isClosing 属性标记为"已关闭",这样就能确保不再处理来自该会话的新请求了。
下面是具体代码:

SessionId

生成系统当前时间的时间戳,64位的long型整数
将时间戳左移24位,在无符号右移8位
经过上一步,该时间戳的高8位全部为0,低56位不为0
接着,将机器编号左移56位,那么机器编号的高8位不为0,低56位全为0
最后,将上面得到的机器编号和时间戳进行或运算
为什么要做这样的位运算?
时间戳经过这样的运算之后,高8位全部为0,和机器编号的高8位进行或运算之后,其结果完全取决于机器编号的高8位;同理,低56位由时间戳决定。因此可以看出,SessionId其实是由机器编号+时间戳唯一决定的。可以保证在单机环境下的唯一性。
为什么右移8位需要采用无符号?

sessionsById:ConcurrentHashMap<Long,SessionImpl>类型,用于根据 sessionID来管理 Session 实体。
sessionsWithTimeout:ConcurrentHashMap<Long,Integer>类型,用于根据SessionId来管理会话的超时时间。该数据结构和ZooKeeper 内存数据库相连通,会被定期持久化到快照文件中去。
sessionExpiryQueue:过期队列。用于维护会话的过期,并且使用bucket来维护会话,每一个bucket对应一个某时间范围内过期的会话。
那么SessionTracker是如何管理会话的呢?下面我们来详细看一看
会话管理
分桶策略
// 保存Session对象与其过期时间额映射关系,key是Session,value过期时间private final ConcurrentHashMap<E, Long> elemMap = new ConcurrentHashMap<E, Long>();// 保存过期时间与在此过期时间点要过期的会话集合的映射关系。// key是过期时间,value是Session集合(会话桶)private final ConcurrentHashMap<Long, Set<E>> expiryMap = new ConcurrentHashMap<Long, Set<E>>();// 下一个过期的时间点private final AtomicLong nextExpirationTime = new AtomicLong();// 过期时间间隔private final int expirationInterval;
下图是分桶策略的示意图:

ExpirationTime = CurrentTime + SessionTimeout
CurrentTime:当前时间,单位是毫秒
SessionTimeout:该会话设置的超时时间,单位也是毫秒。

ExpirationTime_ = CurrentTime + SessionTimeoutExpirationTime = (ExpirationTime_ / ExpirationInterval + 1) * ExpirationInterval
当sessionA在1500ms后过期时,那么其会坐落在(1500/2000+1)*2000=2000ms这个key里。
当sessionB在3000ms后过期时,那么其会坐落在(3000/2000+1)*2000=4000ms这个key里。
当sessionC在5000ms后过期时,那么其会坐落在(5000/2000+1)*2000=6000ms这个key里。
当sessionD在7500ms后过期时,那么其会坐落在(7500/2000+1)*2000=8000ms这个key里。
| 0 | 2000ms | 4000ms | 6000ms | 8000ms |
| SessionA | SessionB | SessionC | SessionD |
从上面看来,session似乎是到了事先计算好的时间就会过期,其实并非如此。为了保持客户端会话的有效性,在ZooKeeper的运行过程中,客户端会在会话超时时间过期范围内向服务端发送 PING 请求来保持会话的有效性,我们俗称"心跳检测"。同时,服务端需要不断地接收来自客户端的这个心跳检测,并且需要重新激活对应的客户端会话,我们将这个重新激活的过程称为TouchSession。会话激活的过程,不仅能够使服务端检测到对应客户端的存活性,同时也能让客户端自己保持连接状态。我们简单讲一下流程:
检查该会话是否被关闭。如果关闭,则不再激活。
计算新的超时时间
迁移会话(从老桶到新桶)
具体方法在SessionTrackerImpl.touch()中,我们来看一下具体的代码:

updateSessionExpiry()方法内部会调用ExpireQueue.update()根据分桶策略将会话放到指定的会话桶中,如果在旧桶中存在当前会话,需要将该会话移动到新桶中,并将旧桶中的该会话删掉。





以上就是Zookeeper在服务端侧针对会话管理的完整处理过程。至于客户端发送请求后到服务端是如何创建会话的,我们放到下一章节来介绍。
敬请期待吧~~






