什么是Zookeeper的会话机制
我们在服务器启动Zookeeper的时候能得知,ZK服务端对外默认端口是2181。而客户端连接到服务端上,其本质其实就是一个TCP连接(长连接) ,当连接正式建立起来的时候,就开起来该次会话的生命周期了。有了会话之后,后续的请求发送,回应,心跳检测等机制都是基于会话来实现的。那对于ZK的服务端来说,如何维护管理这些会话,就是本文要聊的内容啦~
Session相关的基本概念
当连接建立的时候,Session就已经建立起来,与这个过程相关的有三个重要的值:
-
SessionID:会话的唯一标识,由ZK来分配
-
TimeOut:会话超时时间。在客户端与服务端连接的期间,如果因为某些原因断开了连接(如网络中断等等),该次会话以及其相关的临时节点不会被马上删除,而是等待TimeOut耗尽之后,若客户端没有重连上来,那本次会话才会失效,相关的一些临时节点也会被删除
-
Expiration Time:TimeOut是一个相对时间,而Expiration Time则是在时间轴上的一个绝对过期时间。可能你也会想到,一个比较通用的计算方法就是:
E x p i r a t i o n T i m e = C u r r e n t T i m e ( 当 前 时 间 ) + T i m e O u t ( 超 时 时 间 ) ExpirationTime = CurrentTime(当前时间) + TimeOut(超时时间) ExpirationTime=CurrentTime(当前时间)+TimeOut(超时时间)
这样算出来的时间最准确。但ZK可不是这么算的,它用的是
E x p i r a t i o n T i m e = C u r r e n t T i m e + S e s s i o n T i m e O u t ExpirationTime_ = CurrentTime + SessionTimeOut ExpirationTime=CurrentTime+SessionTimeOutE x p i r a t i o n T i m e = ( E x p i r a t i o n T i m e / E x p i r a t i o n I n t e r v a l + 1 ) ∗ E x p i r a t i o n I n t e r v a l ExpirationTime = ( ExpirationTime_/ExpirationInterval + 1 ) * ExpirationInterval 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
为什么要如此区分呢?因为ZK的服务端会在运行期间定时地对会话进行超时检测,如果不对Session进行维护的话,那在检测的时候岂不是要遍历所有的Session?这显然不是一个好办法,所以才以超时时间为维度来存放Session,这样在检测的时候,只需要扫描对应的桶就可以了
那这样的话,新的问题就来了:每个Session的超时时间是一个很分散的值,假设有1000个Session,很可能就会有1000个不同的超时时间,进而有1000个桶,这样有啥意义吗?这就要回头看一下ExpirationTime的计算公式了,再贴一下:
E
x
p
i
r
a
t
i
o
n
T
i
m
e
=
C
u
r
r
e
n
t
T
i
m
e
+
S
e
s
s
i
o
n
T
i
m
e
O
u
t
ExpirationTime_ = CurrentTime + SessionTimeOut
ExpirationTime=CurrentTime+SessionTimeOut
E x p i r a t i o n T i m e = ( E x p i r a t i o n T i m e / E x p i r a t i o n I n t e r v a l + 1 ) ∗ E x p i r a t i o n I n t e r v a l ExpirationTime = ( ExpirationTime_/ExpirationInterval + 1 ) * ExpirationInterval 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结束后就会被服务端扫描到然后进行清楚了