聊聊Zookeeper之会话机制Session

2,629 阅读4分钟

什么是Zookeeper的会话机制

我们在服务器启动Zookeeper的时候能得知,ZK服务端对外默认端口是2181。而客户端连接到服务端上,其本质其实就是一个TCP连接(长连接) ,当连接正式建立起来的时候,就开起来该次会话的生命周期了。有了会话之后,后续的请求发送,回应,心跳检测等机制都是基于会话来实现的。那对于ZK的服务端来说,如何维护管理这些会话,就是本文要聊的内容啦~

Session相关的基本概念

当连接建立的时候,Session就已经建立起来,与这个过程相关的有三个重要的值:

  • SessionID:会话的唯一标识,由ZK来分配

  • TimeOut:会话超时时间。在客户端与服务端连接的期间,如果因为某些原因断开了连接(如网络中断等等),该次会话以及其相关的临时节点不会被马上删除,而是等待TimeOut耗尽之后,若客户端没有重连上来,那本次会话才会失效,相关的一些临时节点也会被删除

  • Expiration Time:TimeOut是一个相对时间,而Expiration Time则是在时间轴上的一个绝对过期时间。可能你也会想到,一个比较通用的计算方法就是:

    ExpirationTime = CurrentTime(当前时间) + TimeOut(超时时间)

    这样算出来的时间最准确。但ZK可不是这么算的,它用的是

    ExpirationTime_ = CurrentTime + SessionTimeOut
    ExpirationTime = ( ExpirationTime_/ExpirationInterval + 1 ) * ExpirationInterval

    ExpirationInterval默认值为2000毫秒,至于为什么要这样算,稍后讲分桶策略的时候再详细聊一下叭~

顺便贴一下SessionId生成的源码,SessionId的生成和两个东西相关联,一个是时间戳,一个是机器id

/**其中id是机器id**/
public static long initializeNextSession(long id) {
        long nextSid = 0;
        nextSid = (System.currentTimeMillis() << 24) >>> 8;
        nextSid =  nextSid | (id <<56);
        return nextSid;
}

分桶机制

Session是由ZK服务端来进行管理的,一个服务端可以为多个客户端服务,也就是说,有多个Session,那这些Session是怎么样被管理的呢?而分桶机制可以说就是其管理的一个手段。ZK服务端会维护着一个个"桶",然后把Session们分配到一个个的桶里面。而这个区分的维度,就是ExpirationTime

img

为什么要如此区分呢?因为ZK的服务端会在运行期间定时地对会话进行超时检测,如果不对Session进行维护的话,那在检测的时候岂不是要遍历所有的Session?这显然不是一个好办法,所以才以超时时间为维度来存放Session,这样在检测的时候,只需要扫描对应的桶就可以了

那这样的话,新的问题就来了:每个Session的超时时间是一个很分散的值,假设有1000个Session,很可能就会有1000个不同的超时时间,进而有1000个桶,这样有啥意义吗?这就要回头看一下ExpirationTime的计算公式了,再贴一下:

ExpirationTime_ = CurrentTime + SessionTimeOut
ExpirationTime = ( ExpirationTime_/ExpirationInterval + 1 ) * ExpirationInterval

可以看到,最终得到的ExpirationTime是ExpirationInterval的倍数,而ExpirationInterval就是ZK服务端定时检查过期Session的频率,默认为2000毫秒。所以说,每个Session的ExpirationTime最后都是一个近似值,是ExpirationInterval的倍数,这样的话,ZK在进行扫描的时候,只需要扫描一个桶即可。

另外让过期时间是ExpirationInterval的倍数还有一个好处就是,让检查时间和每个Session的过期时间在一个时间节点上。否则的话就会出现一个问题:ZK检查完毕的1毫秒后,就有一个Session新过期了,这种情况肯定是不好。

Session激活(续约)

在客户端与服务端完成连接之后生成过期时间,这个值并不是一直不变的,而是会随着客户端与服务端的交互来更新。过期时间的更新,当然就伴随着Session在桶上的迁移

最简单的一点,客户端每向服务端发送请求,包括读请求和写请求,都会触发一次激活,因为这预示着客户端处于活跃状态

而如果客户端一直没有读写请求,那么它在TimeOut的三分之一时间内没有发送过请求的话,那么客户端会发送一次PING,来触发Session的激活。当然,如果客户端直接断开连接的话,那么TimeOut结束后就会被服务端扫描到然后进行清楚了