概述
主要讲述zookeeper客户端的的会话创建的过程,会话创建后服务端对会话是如何管理的。会话创建后,客户端发起读写操作的一整个流程是什么样的呢?
Zookeeper的会话机制
Zookeeper客户端和服务端的读写操作、Watcher通知机制等都是基于会话来实现的,会话本质上是一个TCP的长连接。
长连接的一个好处是创建连接后可以一直传输数据,不像短连接那样传输完数据后就关闭连接,因为网络中不同节点使用TCP协议通过SOCKET进行通信,首先需要3次握手建立连接,数据传输,4次握手断开连接,因此如果频繁的创建、关闭,是很耗费系统资源的。
短连接 : 连接->传输数据->关闭连接
长连接: 连接->传输数据->保持连接 -> 传输数据-> 。。。 ->关闭连接。
考虑到zookeeper的连接特性,一个客户端只会和你配置中的zookeeper server集群中的一台节点建立连接,可能是leader或者follower节点,只需要一个TCP长连接就足以应对,因选择一个TCP长连接,不失为一种最好的方案。
会话生命周期
- 初始化Zookeeper客户端,状态变为CONNECTING
- 客户端选择服务端列表中的一个进行连接,连接成功,状态转换为CONNECTED,Zookeeper客户端和服务端保持心跳连接。
- 由于网络不通或者server节点挂掉等原因,客户端失去了与ZooKeeper服务器的心跳连接,它会转移回CONNECTING状态,同时会尝试寻找另外一台zookeeper服务器。
- 如果在会话过期时间以内找到另外一个服务器或者连接到之前的服务器,并确认了这个Session仍然有效,它会转移回CONNECTED状态。
- 如果超过会话过期时间没有重连成功,状态会变为CLOSED状态,关闭Session。
会话管理
会话是由Zookeeper服务端进行管理的,一个服务端可以有多个Session,那么这些session是如何进行管理的呢?什么时候移除呢?
session属性
首先我们先看下会话Session实体的四个重要属性。
- sessionID 。 会话ID,唯一标识一个会话,每次客户端创建新的会话时,Zookeeper都会为其分配一个全局唯一的sessionID。
- timeOut。会话超时时间,客户端在构造Zookeeper实例时,会配置sessionTimeout参数用于指定会话的超时时间,Zookeeper客户端向服务端发送这个超时时间后,服务端会根据自己的超时时间限制最终确定会话的超时时间。
- Expiration Time。下次会话超时的时间点,是时间轴上的一个绝对时间点,为了便于Zookeeper对会话实行"分桶策略"管理,同时为了高效低耗地实现会话的超时检查与清理,Zookeeper会为每个会话标记一个下次会话超时时间点,其值大致等于当前时间加上TimeOut。
- isClosing。标记一个会话是否已经被关闭,当服务端检测到会话已经超时失效时,会将该会话的isClosing标记为"已关闭",这样就能确保不再处理来自该会话的心情求了。
分桶策略
Zookeeper的会话管理采用分桶策略(将类似的会话放在同一区块中进行管理)进行管理。
Zookeeper服务端基于ExpirationTime过期时间点作为维度维护着一个个“桶”,将相似ExpirationTime的session放在一块,那么在运行期间Zookeeper服务器进行会话超时检查时,只要扫描对应的桶进行检查就行,避免了遍历全部Sesssion, 提高效率。
每个会话的ExpirationTime的计算时间如下:
ExpirationTime = ((CurrentTime + SessionTimeOut) / ExpirationInterval + 1) * ExpirationInterval
- CurrentTime :当前时间点
- SessionTimeOut: session 超时时间
- ExpirationInterval :时间间隔,默认为tickTime的值
Session 激活
ExpirationTime 会话过期时间点并不是一直不变,就想像程我们web应用中的session,每次请求访问都会重新刷新过期时间点,zookeeper也需要重新更新会话的ExpirationTime,这一个过程叫做Session激活。
下面两种方式都会重新触发Session 激活:
- 客户端每向服务端发送请求,包括读请求和写请求,都会触发一次激活
- 而如果客户端一直没有读写请求,那么它在TimeOut的三分之一时间内没有发送过请求的话,那么客户端会发送一次PING,来触发Session的激活。
Session激活流程如上图,大致分为4步:
- 检查该会话是否已经被关闭。若已经被关闭,则直接返回即可。
- 计算该会话新的超时时间ExpirationTime_New。使用上面提到的公式计算下一次超时时间点。
- 获取该会话上次超时时间ExpirationTime_Old。计算该值是为了定位其所在的区块。
- 迁移会话。将该会话从老的区块中取出,放入ExpirationTime_New对应的新区块中。
Zookeeper服务端有专门的会话超时间检查线程,逐个检查会话桶中的会话,针对没有激活已经过期的会话进行删除临时节点,清理会话等操作。
读数据过程
Zookeeper读过程相对简单,客户端Client和server端建立连接后,这个server节点无论时Leader 还是 Follower ,Client发出读请求之后,都会直接返回结果。
但是,Client读操作不能保证读取到的时最新的数据,有可能是旧数据, 如下图的场景。
官网也给出了解释:
写数据过程
写入请求发送到Leader节点
- Client向Leader节点发出写请求
- Leader将数据写入到本节点,并将数据发送到所有的Follower节点
- 当Leader接收到一半以上节点(包含自己)返回写成功的信息之后,表示写入成功。如上图接收到自己以及23箭头的follower节点的写成功消息,超过一半了
- 将写入成功消息返回给客户端
写入请求发送到Follower节点
- Client向Follower节点发出写请求
- Follower节点将请求转发给Leader节点
- Leader将数据写入到本节点,并将数据发送到所有的Follower节点
- 当Leader接收到一半以上节点(包含自己)返回写成功的信息之后,表示写入成功
- Leader节点将写成功信息告知原来提交的Follower节点
- Follower节点将写成功的消息通知给Client
总结
主要讲解了Zookeeper客户端和服务端的一些交互过程,包括会话创建和管理,以及客户端读写的整个流程。