zk的session

zk维护的数据主要有:客户端的会话(session)状态及数据节点(dataNode)信息。zk在内存中构造了个DataTree的数据结构,维护着path到dataNode的映射以及dataNode间的树状层级关系。为了提高读取性能,集群中每个服务节点都是将数据全量存储在内存中。可见,zk最适于读多写少且轻量级数据(默认设置下单个dataNode限制为1MB大小)的应用场景。数据仅存储在内存是很不安全的,zk采用事务日志文件及快照文件的方案来落盘数据,保障数据在不丢失的情况下能快速恢复。上篇文章学习了节点相关的内容,这篇看一下session相关的内容:

指zk客户端与zk服务器之间的会话,在zk中,会话是通过客户端和服务器之间的一个TCP长连接来实现的。通过这个长连接,客户端能够使用心跳检测与服务器保持有效的会话,也能向服务器发送请求并接收响应,还可接收服务器的Watcher事件通知。Session的sessionTimeout,是会话超时时间,如果这段时间内,客户端未与服务器发生任何沟通(心跳或请求),服务器端会清除该session数据,客户端的TCP长连接将不可用,这种情况下,客户端需要重新实例化一个Zookeeper对象。在ZooKeeper客户端与服务端成功完成建立连接后,就建立了一个会话。ZooKeeper会话在整个运行期间的生命周期中,会在不同的会话状态之间进行切换,这些状态一般可以分为CONNECTING、CONNECTED、RECONNECTING、RECONNECTED和CLOSE等。Session 是ZooKeeper中最重要的概念之一。它包括4个基本属性:

  • sessionID:会话ID,唯一标识一个会话,每次客户端创建新会话的时候,ZooKeeper都会为其分配一个全局唯一的sessionID。
  • TimeOut:会话超时时间。客户端在构造ZooKeeper实例的时候,会配置一个sessionTimeOut参数用于指定会话超时时间。ZooKeeper客户端向服务器发送这个超时时间后,服务器会根据自己的超时时间限制最终确定会话的超时时间。
  • TickTime:下次会话超时时间点。为了便于ZooKeeper对会话实行“分桶策略”管理,同时也是为了高效低耗地实现的超时检测与清理,ZooKeeper会为每个会话标识一个下次会话超时时间。
  • isClosing:该属性用于标记一个会话是否被关闭。通常当服务端检测到一个会话已经超时失效的时候,会将该会话的isClosing属性标记为“已关闭”,这样就能确保不再处理来自该会话的新请求了。

Zk客户端通过使用语言绑定(language binding)创建一个service的handle,来和zk service建立session。一旦创建,此handle初始为CONNECTING状态,然后client将会尝试与zk service中的一台server建立链接,链接成功后,状态将会被转换为CONNECTED。在一般情况下,session会是这两个状态中的一种。不过,当发生不可恢复时,例如session过期或者验证失败,或者应用明确的关闭了handle,那么此session的handle将会被变更为CLOSED状态。下图为session状态转换(来自apache zookeeper官网)
在这里插入图片描述
当client从zk service中获取一个handle(句柄)之后,zk将会为client创建一个session,sessionID为一个64位的数字。如果client链接到了其他的server上,它(client)将会把session id作为“握手”链接的一部分发送给server。因为安全的因素,server还为session id创建了一个password,以便任何ZK server都能够验证。当session创建成功后,session id和password都将会发送给client。无论client和哪个server建立链接,它都必须将session id和password一同发送给需要建立链接的server。当client连接失效后,它将会检索指定的server列表,并与其中一个server重新建立链接,session的状态被再次转换为CONNECTED(在session timeout有效期内),或者将会被转换成“EXPIRED”状态(session timeout之后,建立了链接)。不建议在链接失效后,创建新的session(Zookeeper实例),ZK client将会为你处理重链接。此外,ZK内置的一些机制来处理类似“羊群效应”等等。当client被通知session过期,只需要创建一个新的session即可。

Client与Server持续通讯时,也意味着Session是"活跃"的(sessionId将会伴随每次请求交付给server),如果session空闲一段时间,这将会导致过期,所以client会发送一种PING类型的请求来保持session的alive,PING请求不仅可以让server知道client仍然存活,而且它也能够验证当前zk server是否alive

会话创建

这里就讲最底层的会话创建以及会话的数据结构,参照 SessionTrackerImpl.SessionImpl数据结构

    public static class SessionImpl implements Session {
        SessionImpl(long sessionId, int timeout, long expireTime) {
            this.sessionId = sessionId;
            this.timeout = timeout;
            this.tickTime = expireTime;
            isClosing = false;
        }

        final long sessionId;//会话id,全局唯一
        final int timeout;//会话超时时间
        long tickTime;//下次会话的超时时间点,会不断刷新
        boolean isClosing;//是否被关闭,如果关闭则不再处理该会话的新请求

        Object owner;

        public long getSessionId() { return sessionId; }
        public int getTimeout() { return timeout; }
        public boolean isClosing() { return isClosing; }
    }

sessionId唯一性的保证

会话id要保证全局唯一,算法如下

   public static long initializeNextSession(long id) {
        long nextSid = 0;
        nextSid = (System.currentTimeMillis() << 24) >>> 8;
        nextSid =  nextSid | (id <<56);
        return nextSid;
    }

id表示配置在myid文件中的值,通常是一个整数,如1、2、3。该算法的高8位确定了所在机器,后56位使用当前时间的毫秒表示进行随机。

会话管理

主要分为,分桶策略,会话激活,超时检测,会话清理

分桶策略

Zookeeper的会话管理主要是通过SessionTracker来负责,其采用了分桶策略(将类似的会话放在同一区块中进行管理)进行管理,以便Zookeeper对会话进行不同区块的隔离处理以及同一区块的统一处理。

在这里插入图片描述

Zookeeper将所有的会话都分配在不同的区块中,分配的原则是每个会话的下次超时时间点(ExpirationTime)。ExpirationTime指该会话最近一次可能超时的时间点。同时,Zookeeper Leader服务器在运行过程中会定时地进行会话超时检查,时间间隔是ExpirationInterval,默认为tickTime的值,ExpirationTime的计算时间如下:

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

会话激活

会了保持客户端会话的有效性,客户端会在会话超时时间过期范围内向服务端发送PING请求来保持会话的有效性(心跳检测)。同时,服务端需要不断地接收来自客户端的心跳检测,并且需要重新激活对应的客户端会话,这个重新激活过程称为TouchSession。会话激活不仅能够使服务端检测到对应客户端的存货性,同时也能让客户端自己保持连接状态,流程如下
在这里插入图片描述
如上图所示,整个流程分为四步:

  1. 检查该会话是否已经被关闭。若已经被关闭,则直接返回即可。
  2. 计算该会话新的超时时间ExpirationTime_New。使用上面提到的公式计算下一次超时时间点。
  3. 获取该会话上次超时时间ExpirationTime_Old。计算该值是为了定位其所在的区块。
  4. 迁移会话。将该会话从老的区块中取出,放入ExpirationTime_New对应的新区块中。

在上面会话激活过程中,只要客户端发送心跳检测,服务端就会进行一次会话激活,心跳检测由客户端主动发起,以PING请求形式向服务端发送,在Zookeeper的实际设计中,只要客户端有请求发送到服务端,那么就会触发一次会话激活,以下两种情况都会触发会话激活。

  1. 客户端向服务端发送请求,包括读写请求,就会触发会话激活。
  2. 客户端发现在sessionTimeout/3时间内尚未和服务端进行任何通信,那么就会主动发起PING请求,服务端收到该请求后,就会触发会话激活。

超时检测

对于会话的超时检查而言,Zookeeper使用SessionTracker来负责,SessionTracker使用单独的线程(超时检查线程)专门进行会话超时检查,即逐个一次地对会话桶中剩下的会话进行清理。如果一个会话被激活,那么Zookeeper就会将其从上一个会话桶迁移到下一个会话桶中,如ExpirationTime 1 的session n 迁移到ExpirationTime n 中,此时ExpirationTime 1中留下的所有会话都是尚未被激活的,超时检查线程就定时检查这个会话桶中所有剩下的未被迁移的会话,超时检查线程只需要在这些指定时间点(ExpirationTime 1、ExpirationTime 2…)上进行检查即可,这样提高了检查的效率,性能也非常好。

会话清理

当SessionTracker的会话超时线程检查出已经过期的会话后,就开始进行会话清理工作,大致可以分为如下七步。
  1. 标记会话状态为已关闭。由于会话清理过程需要一段时间,为了保证在此期间不再处理来自该客户端的请求,SessionTracker会首先将该会话的isClosing标记为true,这样在会话清理期间接收到该客户端的心情求也无法继续处理了。
  2. 发起会话关闭请求。为了使对该会话的关闭操作在整个服务端集群都生效,Zookeeper使用了提交会话关闭请求的方式,并立即交付给PreRequestProcessor进行处理。
  3. 收集需要清理的临时节点。一旦某个会话失效后,那么和该会话相关的临时节点都需要被清理,因此,在清理之前,首先需要将服务器上所有和该会话相关的临时节点都整理出来。Zookeeper在内存数据库中会为每个会话都单独保存了一份由该会话维护的所有临时节点集合,在Zookeeper处理会话关闭请求之前,若正好有以下两类请求到达了服务端并正在处理中。

  • 节点删除请求,删除的目标节点正好是上述临时节点中的一个。
  • 临时节点创建请求,创建的目标节点正好是上述临时节点中的一个。

对于第一类请求,需要将所有请求对应的数据节点路径从当前临时节点列表中移出,以避免重复删除,对于第二类请求,需要将所有这些请求对应的数据节点路径添加到当前临时节点列表中,以删除这些即将被创建但是尚未保存到内存数据库中的临时节点。
  4. 添加节点删除事务变更。完成该会话相关的临时节点收集后,Zookeeper会逐个将这些临时节点转换成"节点删除"请求,并放入事务变更队列outstandingChanges中。
  5. 删除临时节点。FinalRequestProcessor会触发内存数据库,删除该会话对应的所有临时节点。
  6. 移除会话。完成节点删除后,需要将会话从SessionTracker中删除。
  7. 关闭NIOServerCnxn。最后,从NIOServerCnxnFactory找到该会话对应的NIOServerCnxn,将其关闭。

参考地址:

https://www.jianshu.com/p/3b7f9a032ded

https://juejin.im/post/5d2d33b5e51d4510803ce461

https://www.jianshu.com/p/594129a44814

https://www.cnblogs.com/aoshicangqiong/p/8024333.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章