2.ZooKeeper分桶策略实现高性能的会话管理「第五章 ZooKeeper 原理」「架构之路ZooKeeper理论和实战」

前言

         在前面的小节中,我们介绍了ZK的Session的基本原理,其中有一个属性是这么描述的:

TickTime:下次会话超时时间点,默认 2000 毫秒。可在 zoo.cfg 配置文件中配置,便于 server 端对 session 会话实行分桶策略管理。

         这里对Session会话实行分桶策略管理是什么意思呐?这就是本节要重点来介绍的。

一、分桶策略概述

1.1 什么是分桶策略

Zookeeper的session管理主要是通过SessionTracker来负责,其采用了分桶策略进行管理。

分桶策略是指,将空闲超时时间相近的会话放到同一个桶中来进行管理,以减少管理的复杂度。在检查超时时,只需要检查桶中剩下的会话即可,因为没有超时的会话已经被移出了桶,而桶中存在的会话就是超时的会话。

         zk 对于会话空闲的超时管理并非是精确的管理,即并非是一超时马上就执行相关的超时操作。

1.2 什么是分桶策略辅助理解

         通过分桶策略的定义,我们可以看出分桶策略中有两个桶,这两个桶在代码层面的表现形式就是两个Map数据类型,那么我们就会想Map的key是什么?接着往下看,我们慢慢就会揭开TA神奇的面纱。

二、分桶策略原理

2.1 分桶策略图示解说

那在每个红点所在的时间点都要运行一次‘session过期任务’,而且一次任务的执行,只使得一个session过期。这样效率不高。而且如果session比较多的话,那“session过期任务”的执行会占用很大的负载。

ZooKeeper为了方便管理session超时时间,使用分桶策略,使用如下计算公式,来计算session的超时时间:

  1. long currentTime = Time.currentElapsedTime();
  2. long sessionTimeout = 10000;
  3. long expireTime_0 = currentTime + sessionTimeout;
  4. long expireTime = (expireTime_0 / expirationInterval + 1) * expirationInterval;

此计算公式,会根据expirationInterval(默认2000毫秒)把时间分成相同时间段,让处于同一时间段的session的超时时间移动到此时间段结束的时间点上。

如上例子,这三个session的超时时间都变为ExpirationTime2。也就是在ExpirationTime1到ExpirationTime2之间的session,超时时间都设置为ExpirationTime2,这样当currentTime到达ExpirationTime2的时刻,执行一次session过期任务,就把这三个session都给设置为过期了。

         这样处理会更加方便。

2.2 分桶类ExpiryQueue整体工作流程图

 

2.2.1 ExpiryQueue作用

ZooKeeper服务端管理客户端会话超时使用到ExpiryQueue,用来管理Session超时的会话。

2.2.2 ExpiryQueue类图

 

该类中主要包含了以下变量:

(1)nextExpirationTime(下一个过期的时间点) ;

(2)expirationInterval(过期时间间隔) ;

(3)elemMap(Session对象集合,key是session对象,类型是SessionImpl,value为过期时间);

(4)expiryMap(过期的Session对象集合,key为过期时间,value为session)

该类中主要方法:

(1)构造方法初始化nextExpirationTime

(2)update增加或更新session的过期时间

(3)remove清除过期session

(4)getWaitTime判断当前时间是否已经超过了nextExpirationTime,超过返回0,没有超过返回nextExpirationTime-now,zookeeper中通过不停的轮询这个方法来判断是否清除过期session

(5)poll拉取过期session进行清除

2.2 分桶类ExpiryQueue数据结构理解

ExpiryQueue根据expirationInterval将时间分段,将每段区间的时间放入对应的一个集合进行管理。如图二所示,时间段在1503556830000-1503556860000中的数据将会放到1503556860000对应的集合中,1503556860000-1503556890000中的数据将会放到1503556890000的集合中,以此类推。

         在ExpiryQueue的数据结构中,图中的集合由ConcurrentHashMap<Long, Set<E>>进行管理,其中的Key值为到期时间。

         数据分段使用公式为:(当前时间(毫秒)/ expirationInterval + 1)* expirationInterval。该公式表示将当前时间按照expirationInterval间隔算份数,算完后再加一个份额,最后再乘以expirationInterval间隔,就得出了下一个到期时间。

三、分桶策略源码解析

         我们来看看源码的实现。

3.1 SessionTrackerImpl#touchSession

分桶策略的实现,主要是 SessionTrackerImpl#touchSession 方法,通俗说这个方法是给session续期的。我们先来阅读此方法的源码:

此代码的逻辑:

(1)调用touchSession(sessionId , timeout)方法,先从 sessionsById 这个map中通过 sessionId 取出session1(下面的update会此session1传入);

(2)然后调用updateSessionExpiry(s,timeout)进行续期。

         我们跟进updateSessionExpiry(s,timeout):

         从这里我可以看出来,session续期最终是交给了sessionExpiryQueue来进行管理,我们通过定义可以看到是ExpiryQueue<SessionImpl>,也是就是实现类是ExpiryQueue。

3.2 ExpiryQueue

         我们看下ExpiryQueue的update是怎么续期的。






         在此之前,我们先来看下ExpiryQuqueue类的情况:

(1)nextExpirationTime(下一个过期的时间点) ;

(2)expirationInterval(过期时间间隔) ;

(3)elemMap(Session对象集合,key是session对象,类型是SessionImpl,value为过期时间);

(4)expiryMap(过期的Session对象集合,key为过期时间,value为session)

3.3 ExpiryQueue#update

         对于ExpiryQueue有了基本的认知之后,我们就可以来看看update这个方法是怎么续期的了:

此代码逻辑说明:

(1)eleMap.get(elem):这个的E代表的是SessionImpl ,这个通过上面的说到的定义中ExpiryQueue<SessionImpl>可以看出来;所以这个就是通过会话对象获取到上一期的过期时间点。

(2)通过roundToNextInterval(time)计算下一个过期时间点:

(3)把session1 从expirationTime1 移动到 expirationTime2:(代码上就是先从expirationTime1的sessionSet中移除session1;然后把session1 添加到expirationTime2的sessionSet中。)

3.3 session过期任务线程

         过期任务主要是由SessionTrackerImpl的run来进行处理的:

在 SessionTrackerImpl 中的session超时任务中,只是把 session 的isClosing 变量设置为true。

只把session标识位isClosing是不够的,如果此session对应的client创建了临时节点,还需要把临时节点删掉呢。所以要调用 expirerexpire(s); 处理session过期逻辑的:

org.apache.zookeeper.server.ZooKeeperServer#createSessionTracker

         可以找到ZooKeeperServer的expire(Session): 

         此方法处理session过期逻辑:调用的是 ZooKeeperServer#close 方法,close方法中又调用了 ZooKeeperServer#submitRequest提交请求 方法,其中 操作类型为OpCode.closeSession。

         也就是 ZooKeeperServer 处理session 过期,是提交了一个操作类型为 为OpCode.closeSession的request,

3.4 session什么时候被移出掉呢?

         session标识位isClosing = true后,是在哪里把session从sessionSet中remove掉的呢?

以为 调用 expirer.expire(s); 就是为了把session从 sessionSet中remove掉。

所以的请求处理器,都只是把session 的isClosing赋值为true。

其实,在 touchSession()方法中有一个前提:就是session不能是isClosing 状态的,否则就直接返回:

四、小结

         这个可能有点复杂,大家可以根据源码自己进行分析的。

         要知道的核心就是:

(1)分桶策略的实现就是两个HashMap的容器来进行实现的。

(2)核心的两个方法就是SessionTrackerImpl的touchSession()和run()方法。 



购买完整视频,请前往:http://www.mark-to-win.com/TeacherV2.html?id=287