背景
Zookeeper作为一个服务器,需要管理与客户端的连接和会话,如接受连接,网络IO,和过期清理等,此文章就介绍下Zookeeper的过期策略.
注:目前网络上的资料介绍的都是会话的过期策略,在最新版Zookeeper中,将过期策略的代码抽象为ExpiryQueue
类,除了会话,连接也用此类进行过期处理,但为了行文方便,下文中一律使用会话进行介绍
注:在下文中使用过期时间表示会话或连接何时过期,对应的英文为expiration time
,用unix时间戳表示.超时时间表示会话或连接的超时时间,对应的英文为timeout
,如5000ms
简单的会话清理策略
在介绍分桶策略之前,我先介绍一种比较简略的会话清理策略.
数据结构
使用3个TreeMap
作为数据结构,如下:
- timeoutMap:其key是session,value是该会话的timeout(用于保存会话的timeout)
- elementMap:其key是session,value是该回话的过期时间(expiration time)(过期时间都是在当前时间之后的)
- expiryMap:其key是过期时间,value是原定在该过期时间过期的session
更新会话过期时间
客户端与服务端通信过程中更新上述3个数据结构,流程如下:
- 会话初始化时将其加入到timeoutMap,保存该会话的timeout
- 每当服务端与该会话进行通信时,更新会话的过期时间,将其过期时间修改为currentTime+timeout,包括2个步骤
- 通过elementMap获取该session的prevExpiryTime,根据prevExpiryTime在expiryMap删除其对应的session
- 修改elementMap,将session对应的过期时间修改为currentTime+timeout
清理过期会话
后台启动一个清理线程清理过期的session,线程中无限循环,执行工作如下:
- 执行定时的wait()操作,等待到当前时间为expiryMap中最小的key(使用TreeMap的原因),清理该key对应的session.
可能有同学对清理线程中执行的操作不是非常理解,由于expireMap的key是过期时间,当前时间到了过期时间,对应的session就要被清除.若在此之前,session与服务端进行了通信,就会更新expiryMap
,删除该session原本的过期时间,该session就不会被清理线程清理.
优缺点
缺点:
- 若有多个session在同一时间过期,则
exipryMap
中只保存了最后一个调用put(time,session)方法的session的信息
解决方案:将exipryMap
的value修改为Set<Session>
- 若存在大量会话,则清理线程会在短时间内执行大量的wait()操作,虽然不知道会有什么不足,但总感觉有问题,比如系统能在短时间内调度大量的wait()操作吗?
优点:
- 实时性高
分桶策略
为了解决上述策略的缺点2,就引入了分桶策略,其将过期时间在某个时间段内的会话将看做一组,在每组最后的时间点,即下图中expiration time n,批量清理此组的过期会话.此时间段的长度可适当调整,若过大,则效率较高,实时性不足;若过小,则效率较低,实时性高.