huihuajizhi.md 6.4 KB
Newer Older
沉默王二's avatar
沉默王二 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
ZooKeeper 的工作方式一般是通过客户端向服务端发送请求而实现的。

而在一个请求的发送过程中,首先,客户端要与服务端进行连接,而一个连接就是一个会话。

> ❝
> 
> 在 ZooKeeper 中,一个会话可以看作是一个用于表示客户端与服务器端连接的数据结构 Session。
> 
> ❞

这个数据结构由三个部分组成:分别是会话 ID(sessionID)、会话超时时间(TimeOut)、会话关闭状态(isClosing)

*   会话 ID:会话 ID 作为一个会话的标识符,当我们创建一次会话的时候,ZooKeeper 会自动为其分配一个唯一的 ID 编码。

*   会话超时时间:一般来说,一个会话的超时时间就是指一次会话从发起后到被服务器关闭的时长。而设置会话超时时间后,服务器会参考设置的超时时间,最终计算一个服务端自己的超时时间。而这个超时时间则是最终真正用于 ZooKeeper 中服务端用户会话管理的超时时间。

*   会话关闭状态:会话关闭 isClosing 状态属性字段表示一个会话是否已经关闭。如果服务器检查到一个会话已经因为超时等原因失效时, ZooKeeper 会在该会话的 isClosing 属性值标记为关闭,再之后就不对该会话进行操作了。

**「会话状态」**

在 ZooKeeper 服务的运行过程中,会话会经历不同的状态变化。

这些状态包括:

> ❝
> 
> 正在连接(CONNECTING)、已经连接(CONNECTIED)、正在重新连接(RECONNECTING)、已经重新连接(RECONNECTED)、会话关闭(CLOSE)等。
> 
> ❞

当客户端开始创建一个与服务端的会话操作时,它的会话状态就会变成 CONNECTING,之后客户端会根据服务器地址列表中的服务器 IP 地址分别尝试进行连接。如果遇到一个 IP 地址可以连接到服务器,那么客户端会话状态将变为 CONNECTIED。

如果因为网络原因造成已经连接的客户端会话断开时,客户端会重新尝试连接服务端。而对应的客户端会话状态又变成 CONNECTING ,直到该会话连接到服务端最终又变成 CONNECTIED。

> ❝
> 
> 在 ZooKeeper 服务的整个运行过程中,会话状态经常会在 CONNECTING 与 CONNECTIED 之间进行切换。
> 
> ❞

最后,当出现超时或者客户端主动退出程序等情况时,客户端会话状态则会变为 CLOSE 状态。

**「会话异常」**

在 ZooKeeper 中,会话的超时异常包括客户端 readtimeout 异常和服务器端 sessionTimeout 异常。

*   在我们平时的开发中,要明确这两个异常的不同之处在于一个是发生在客户端,而另一个是发生在服务端。

而对于那些对 ZooKeeper 接触不深的开发人员来说,他们常常踩坑的地方在于,虽然设置了超时间,但是在实际服务运行的时候 ZooKeeper 并没有按照设置的超时时间来管理会话。

*   这是因为 ZooKeeper 实际起作用的超时时间是通过客户端和服务端协商决定。

ZooKeeper 客户端在和服务端建立连接的时候,会提交一个客户端设置的会话超时时间,而该超时时间会和服务端设置的最大超时时间和最小超时时间进行比对,如果正好在其允许的范围内,则采用客户端的超时时间管理会话。

如果大于或者小于服务端设置的超时时间,则采用服务端设置的值管理会话。

**「分桶策略」**

我们知道在 ZooKeeper 中为了保证一个会话的存活状态,客户端需要向服务器周期性地发送心跳信息。

*   而客户端所发送的心跳信息可以是一个 ping 请求,也可以是一个普通的业务请求。

ZooKeeper 服务端接收请求后,会更新会话的过期时间,来保证会话的存活状态。

*   所以在 ZooKeeper 的会话管理中,最主要的工作就是管理会话的过期时间。

> ❝
> 
> ZooKeeper 中采用了独特的会话管理方式来管理会话的过期时间。
> 
> ❞

在 ZooKeeper 中,会话将按照不同的时间间隔进行划分,超时时间相近的会话将被放在同一个间隔区间中,这种方式避免了 ZooKeeper 对每一个会话进行检查,而是采用分批次的方式管理会话。

这就降低了会话管理的难度,因为每次小批量的处理会话过期也提高了会话处理的效率。

**「ZooKeeper 这种会话管理的好处?」**

ZooKeeper 这种分段的会话管理策略大大提高了计算会话过期的效率,如果是在一个实际生产环境中,一个大型的分布式系统往往具有很高的访问量。

而 ZooKeeper 作为其中的组件,对外提供服务往往要承担数千个客户端的访问,这其中就要对这几千个会话进行管理。

在这种场景下,要想通过对每一个会话进行管理和检查并不合适,所以采用将同一个时间段的会话进行统一管理,这样就大大提高了服务的运行效率。

**「底层实现」**

ZooKeeper 底层实现的原理,核心的一点就是过期队列这个数据结构。所有会话过期的相关操作都是围绕这个队列进行的。

*   可以说 ZooKeeper 底层就是采用这个队列结构来管理会话过期的。

**「一个会话过期队列是由若干个 bucket 组成的。」**

*   bucket 是一个按照时间划分的区间。

*   在 ZooKeeper 中,通常以 expirationInterval 为单位进行时间区间的划分,它是 ZooKeeper 分桶策略中用于划分时间区间的最小单位。

*   在 ZooKeeper 中,一个过期队列由不同的 bucket 组成。

*   每个 bucket 中存放了在某一时间内过期的会话。

将会话按照不同的过期时间段分别维护到过期队列之后,在 ZooKeeper 服务运行的过程中,具体的执行过程如下图所示。

![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/zookeeper/huihuajizhi-1.png)


首先,ZooKeeper 服务会开启一个线程专门用来检索过期队列,找出要过期的 bucket,而 ZooKeeper 每次只会让一个 bucket 的会话过期,每当要进行会话过期操作时,ZooKeeper 会唤醒一个处于休眠状态的线程进行会话过期操作,之后会按照上面介绍的操作检索过期队列,取出过期的会话后会执行过期操作。


整理:沉默王二,戳[转载链接](https://mp.weixin.qq.com/s/-evZg0epRrOr1IwQ3GJ2Zg),作者:月伴飞鱼,戳[原文链接](https://mp.weixin.qq.com/s/B2ngp0q5kdWsCNH8sw_5DA)