Zookeeper的关键机制的实现原理

1、zookeeper的会话管理

会话(Session)是Zookeeper的一个重要的抽象。保证请求有序、临时znode节点、监事点都与会话密切相关。因此 会话的跟踪机制 对ZooKeeper来说也非常重要。ZooKeeper服务器的一个重要任务就是跟踪并维护这些会话。 在独立模式下,单个服务器会跟踪所有的会话,而在仲裁模式下则由群首服务器来跟踪和维护。 群首服务器和独立模式的服务器实际上运行相同的会话跟踪器(参考SessionTracker类和SessionTrackerImpl类)。而 追随者服务器仅仅是简单地把客户端连接的会话信息转发给群首服务器 (参考LearnerSessionTracker类)。
为了保证会话的存活,服务器需要接收会话的心跳信息。心跳的形式可以是一个新的请求或者显式的ping消息(参考LearnerHandler.run())。两种情况下,服务器通过更新会话的过期时间来触发(touch)会话活跃(参考SessionTrackerImpl.touchSession()方法)。 在仲裁模式下,群首服务器发送一个PING消息给它的追随者们,追随者们返回自从最新一次PING消息之后的一个session列表 群首服务器每半个tick(参考10.1.1节的介绍)就会发送一个ping消息给追随者们。所以,如果一个tick被设置成2秒,那么群首服务器就会每一秒发送一个ping消息。
对于管理会话的过期有两个重要的要点。一个称为过期队列(expiry queue)的数据结构(参考ExpiryQueue类),用于维护会话的过期。这个数据结构使用bucket来维护会话,每一个bucket对应一个某时间范围内过期的会话,群首服务器每次会让一个bucket的会话过期。为了确定哪一个bucket的会话过期,如果有的话,当下一个底限到来时,一个线程会检查这个expiry queue来找出要过期的bucket。这个线程在底限时间到来之前处于睡眠状态,当它被唤醒时,它会取出过期队列的一批session,让它们过期。当然取出的这批数据也可能是空的。为了维护这些bucket,群首服务器把时间分成一些片段,以expirationInterval为单位进行分割,并把每个会话分配到它的过期时间对应的bucket里,其功能就是有效地计算出一个会话的过期时间,以向上取正的方式获得具体时间间隔。更具体来说,就是对下面的表达式进行计算,当会话的过期时间更新时,根据结果来决定它属于哪一个bucket。
(expirationTime / expirationInterval + 1) * expirationInterval
举例说明,比如expirationInterval为2,会话的超时时间为10。那么这个会话分配到bucket为12((10/2+1)*2的结果)。注意当我们触发(touch)这个会话时expirationTime会增加,所以随后我们需要根据之后的计算会话移动到其他的bucket中。使用bucket的模式来管理的一个主要原因是为了减少让会话过期这项工作的系统开销。在一个ZooKeeper的部署环境中,可能其客户端就有数千个,因此也就有数千个会话。在这种场景下要细粒度地检查会话过期是不合适的。如果expirationInterval短的话,那么ZooKeeper就会以这种细粒度的方式完成检查。目前xpirationInterval是一个tick,通常以秒为单位。

2、zookeeper的watcher管理

zookeeper的watcher实现原理也挺简单的,就是在zookeeper client和zookeeper server上都保存一份对应的watcher对象。 每个zookeeper机器都会有一份完整的node tree数据和watcher数据 每次leader通知follower/observer数据发生变更后,每个zookeeper server会根据自己节点中的watcher事件推送给响应的zookeeper client 每个zk client收到后再根据内存中的watcher引用,进行回调。

3、Session跟踪

关于Session跟踪,ZK server有一个SessionTracker(Leader有SessionTrackerImpl,Follower有LearnerSessionTracker), SessionTrackerImpl端维护如下3个Map结构,Session创建后相关数据分别放入这三个Map中:
  • Map<Long[sessionId],Session>sessionsById
  • Map<Long[sessionId],Integer>sessionsWithTimeout
  • Map<Long[tickTime], SessionSet> sessionSets
    其中sessionsById简单用来存放Session对象及校验sessionId是否过期。
    sessionsWithTimeout用来维护,session的持久化:数据会写入snapshot,在Server重启时会从snapshot恢复到sessionsWithTimeout,从而能 够维持跨重启的session状态。它的key为sessionId,value为client传递的sessionTimeout。
    LearnerSessionTracker维持了2个Map结构:
  • HashMap<Long, Integer> touchTable = new HashMap<Long, Integer>();
  • ConcurrentHashMap<Long, Integer> sessionsWithTimeouts;
     touchTable维持了连接此server的session列表,sessionWithTimeouts维持了所有session,用于snapshot和session恢复。LearnerSessionTracker是一个shell(壳),接受creatSession/closeSession时对上述2个map做调整,并在ping时把本地活跃的session(即touchTable)发送leader。
    Session对象的tickTime属性表示session的过期时间,新建的session此值为0。sessionSets这个Map会以过期时间为key,将所有过期时间 相同的session收集为一个集合。Server每次接到Client的一个请求或者心跳时,会根据当前时间和其sessionTimeout重新计算 过期时间并更新Session对象和sessionSets。计算出的过期时间点会向上取整为ZKServer的属性tickTime的整数倍。 Server启动时会启动一个独立的线程负责将大于当前时间的所有tickTime对应的Session全部清除关闭。SessionTrackerImpl是一个线程(Leader运行),在run()方法中,轮询执行,每次都重新计算sessionSet的过期时间(nextExpirationTime += expirationInterval),如果 nextExpirationTime比当前时间小,就等待(等待指定时间之后,扫描--->再等待-->),到期后开始删除此时间点相应的session集合,同时指示zookeeper.expire向Leader发送closeSession请求。NIOServerCnxn是server端处理请求的起始类,最终的请求将会有相应的ZK Server处理(ZookeeperServer,FollowerZooKeeperServer,LeaderZooKeeperServer,ObserverZooKeeperServer,ReadOnlyZooKeeperServer),在处理请求数据时(包括ping),将会出发sessionTracker.touchSession(id,to),此方法就是负责“延续”session过期处理的,计算过期的代码(time / expirationInterval + 1) * expirationInterval。
对于Client和Server建立连接,就会触发Session的创建,Follower将createSession请求交付给Leader,Leader收到请求后,会发起一个createSession的Proposal,如果表决成功(多数派),最终所有的Server都会在其内存中建立 同样的Session。ZookeeperServer.processTxn(),可知在createSession/closeSession类型时,会对本地zkdatabase进行处理(因为Session信息会被持久存出在ZKDatabase中)。等表决通过后,与客户端建立连接的Server为这个session生成一个password,连同 sessionId,sessionTimeOut一起返回给客户端(ConnectResponse)。任何一个Session只能被一个Server所服务,Leader会保留每个Session被哪个Server所持有.
    Follower.processPacket()是处理Folower与Leader之间的各种通信的(Leader发给Follower),其中有个ping,由此可见,在Follower向Leader发送ping消息时,同时会把本地session集合(LearnerSessionTracker中的touchTable)发送给Leader,在LearnerHandler线程中run方法轮询处理Follower发送给Leader的消息,在对待ping类型消息时读取follower发送的session集合,依次遍历,执行touch方法(即sessionTrackerImpl的touch方法,目的是“延续”session过期时间),不过同时需要注意Leader.lead()方法会向Learner发送ping,不过是空的packet。由此可见,session的状态,完全有leader保持,follower只是定期通过ping把自己维护的session信息包括过期时间发送给leader,leader决定是否过期,以及发送closeSession提议。
    Leader会周期性的检测全局Session列表,是否有过期的,如果有,将会向所有的Follower发送cloaseSession提议,Follower在接收到提议后,将Session删除.
    客户端如果需要重连Server,可以新建一个 ZooKeeper实例,将上一个成功连接的ZooKeeper对象的sessionId和password传给Server:
ZooKeeper zk = new ZooKeeper(serverList, sessionTimeout, watcher, sessionId,passwd):ZKServer会根据sessionId和password为同一个client恢复session,如果还没有过期的话。

  1. Client是否首次连接,主要基于sessionId的判断,对于首次连接(新会话),那么sessionId为0,对于重连接操作,sessionId基于现有的.
  2. createSession过程有Leader操作,Follower只需要把此信息转发给Leader即可.leader根据目前现有的最大SessionId + 1,作为新会话的sessionId,并将信息保存在sessionsWithTimeout,sessionsById,sessionSets这三个数据结构中(参见上述);其中sessionsWithTimeout是ZKDatabase的一部分,如果createSession提议被集群确认,那么session信息将会被持久存在所有server的ZKDatabase中.
  3. createSession创建成功后,Follower即可以向Client反馈结果,并交付sessionId,同时表示和Client之间的连接即可以处理正常的业务.
  4. 对于reopenSession稍微复杂,多了一步session的过期校验,校验的手段非常简单,就是从当前的session列表中查找,如果sessio还存在,则表示未过期..不过此过程仍然有Leader控制.
  5. LeaderZookeeper类使用了SessionTracker跟踪session,此tracker是一个线程,周期性的检测session过期情况.对于createSession或者reopenSession会导致sessionsWithTimeout,sessionsById,sessionSets三个数据结构中session信息的添加,其中sessionSets是个Map(参见上述),key为tickTime,这个值为session下一次过期的时间戳(System.currentTimeMillis() + timeout,并对"expirationInterval"取整).其主要目的就是把所有的session按照即将过期的时间梯度分类,并递增的逐次去检测过期.
  6. Follower/Observer也持有了一个LearnerSessionTracker,这个tracker和Leader持有的SessionTracker有却别,它不是一个线程,它不周期性的检测session过期.它只负责当client请求时,更新session的过期时间.并且在Follower/Observer与Leader的ping消息中,将在自己server上活跃(与当前server建立连接的Session)的session列表发送给Leader;Leader被动的根据此session列表来延迟Session过期操作;说白了,session的活跃性与否是Follower知道,但是Follower需要告诉Leader哪些是活跃的,最终有Leader来检测过期与否.
  7. 对于Leader接收到Follower/Observer的活跃session列表之后,将会操作SessionTracker中的sessionSets,并将活跃的session从此当前过期梯度的sessionSets中移除,因此在SessionTracker的下一次过期检测时就不会得到它们..(如果session活跃,那么tracker将会把此session放在当前过期梯度的下一个梯度中,每个梯度的时间差为一个"expirationInterval").
  8. 对于Leader : SessionTrackerImpl + LeaderZookeeperServer;对于Follower: LearnerSessionTracker + LearnerZookeeperServer

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值