我们的 Leader 已经选举出来了,那接下来该干什么呢?你或许很快能想到,那就是数据同步。通俗地讲,就是 Leader 选出来了,各自的角色都确定好了,那 Follower 和 Observer 自然要同 Leader 建立连接同步数据,这里就引入了 ZooKeeper 的另一个核心知识:Session。
一、什么是 Session?
Session 的概念大家都不陌生,学过 Servlet 的同学一定都背过一道面试题:Cookie 和 Session 的区别是什么?
我通过一个案例来阐述下。
我们都知道浏览器打开一个页面采取的是 HTTP 协议,HTTP 的特点就是无状态
,也就是说每一次请求之间没有任何关联,这样的好处肯定是快,但是也带来了一个问题。举个最简单的例子:评论功能需要先登录,那么我登录是 https://www.xxx.com/login.html
,登录完后跳转到了帖子页面https://www.xxx.com/111.html
,这两页面完全不是一个东西,而 HTTP 又是无状态的,所以即使我登录成功了,也无法将登录态带到帖子页面,我在帖子页面评论的时候仍然会被认为是未登录的。
这咋办呢?这时候 Cookie 就诞生了,Cookie就是把少量的信息存到用户自己电脑上,也就是存储在客户端。Cookie 的作用域是在一个域名下都可以获取到,因为我们可以在登录后将用户登录态存储到www.xxx.com
这个域名下,然后在访问其他页面的时候都先读一下 Cookie,这样就解决了 HTTP 无状态所带来的这个登录态的问题。
这一切看起来都很完美,但是问题在于 Cookie 是存储在客户端的,而且 Cookie 存储的内容是有大小限制的,所以上面我说存储少量的信息到用户自己电脑上。这就带来很多问题了,比如:用户浏览器设置的自动清除 Cookie,那这样不就完犊子了吗?再比如:Cookie 是存到用户自己电脑上,那岂不是可以随意修改?信息不安全啦。
好吧,Cookie 的生涯只能结束了,Session 应运而生,这也是要出现 Session 的主要原因。
其实,Cookie 的主要问题就在于它存储在客户端,那我们 Session 存储在服务端不就好了吗?存储在服务端,大小可控而且安全。我们在服务端生成 Session,然后为其生成一个对应的 SessionId 返回给客户端,通信期间带上 SessionId 做唯一标识符,也就是说一个用户生成一个一个 SessionId,贯穿整个请求。当然 Session 还有很多其他特性,比如 Session 带过期时间、带垃圾回收等内容,这些感兴趣的小伙伴可以自行了解下~
Session 是什么以及为什么要有 Session,想必你已经很清楚了。那我们接下来就步入正题,看看 ZooKeeper 的 Session 又是什么。
二、ZooKeeper 的 Session
老规矩,我们不会直接剖析 ZooKeeper 现有的 Session 源码,而是先剖析其原理、核心思想以及核心交互流程,搞懂这些后我们再剖析源码会事半功倍。所以,你可以把本篇文章想象成为一个需求文档的分析与设计的过程,首先我们需要搞懂需求,也就是需求分析
:ZooKeeper 的 Session 是什么?什么时候需要用到 Session?
我们已经知道 Session 是会话的意思,我们还知道了 Leader 选举的全过程以及选举完成后接下来要做什么事情(数据同步),数据同步自然是需要通过网络来完成,也就是需要客户端与 ZooKeeper 服务端建立连接进行网络通信传输数据,这里我们就需要用到 Session。我们还知道 ZooKeeper 客户端可以进行 CRUD,需要和服务端建立连接,这时候也需要产生一个 Session,代表客户端和服务端的一次通信会话。
那要如何设计这个 Session 呢?
我们可以先采取笼统的方式概括出 Session 的几个核心必备功能
,然后再逐个展开分析。
Session 首先要有 SessionId,这个 SessionId 和 HTTP 的 Session 是一个道理,都是用于客户端和服务端通信之间产生 Session 的唯一标识。举个例子:现在 Leader 选举完成了,大家都知道谁是 Leader、谁是 Follower 了,那么 Follower 要和 Leader 建立网络连接进行数据同步,这时候服务端就会创建一个 Session 并且产生一个 SessionId,然后记录下来。大致情况如下图所示:
那 Session 什么时候结束?很简单想到,客户端和服务端断开连接的时候结束,所以这时候就有两种情况:客户端主动向服务端发信号断开和服务端主动向客户端发信号断开,但是这样就靠谱了吗?我们想想我们的各种连接,比如 HTTP 连接、数据库连接、Redis 连接等,都会设置一个超时时间,防止长时间占用资源不使用的情况,避免资源浪费。我们 Session 也同理,需要有个过期时间,便于我们找出要过期的客户端连接,给释放掉。所以,我们在创建 Session 的时候要为其设置一个过期时间,如下图:
那我们怎么知道 3s 后有哪些 Session 过期呢?我们肯定需要一个“定时任务”,定时去扫描一次,看看有哪些过期的 Session,然后将其销毁,释放连接。我们 ZooKeeper 配置文件zoo.cfg
就有这样的一个配置项:tickTime
,这个配置项就是配置多少毫秒检测一次。假设我们配置的是 3000 毫秒,SessionId 为 111 的过期时间为 6000 毫秒,那么如下时间轴:
按照上图所示,当前时间轴处于1231000
位置,每隔 3000ms 就检查一次,意味着 6000ms 后(执行两次后),会扫描到SessionId111
过期,然后将其 Session 回收,以及连接断开。
那这时候问题来了,我们知道 ZooKeeper 有个tickTime
配置用于设置间隔多久检查一次,如果 Session 的过期时间不是tickTime
设置值的倍数会咋样?如下图:
这样的话,看起来 Session 的过期时间不准确,实际的过期时间会大于等于所设置的过期时间。比如,上面图的含义是SessionId=222
的 Session 以为自己在时间轴为1235000
的时候就过期了,但是实际在1235000
的时候没有触发扫描任务,在1237000
的时候才会扫描,因此它的实际过期时间为1237000
。
这样不太友好,ZooKeeper 采取的手法是按照tickTime
的倍数生效。也就是说,比如tickTime
设置的是 3000ms,而且SessionId=1
设置的是 4000ms 过期,那么SessionId=1
的实际过期时间就是 6000ms 的位置;再比如SessionId=2
设置的是 8000ms 过期,那么SessionId=2
的 Session 实际过期时间是 9000ms 的位置。那如果SessionId=3
设置的 2000ms 过期呢?那对不起,这个SessionId=3
实际过期时间就是 3000ms。完整情况如下图:
所以,Session 过期时间建议设置成tickTime
的倍数,即使不这么设置的话,那实际的过期时间也是tickTime
的倍数。
但如果这样设计的话还是会有 Bug,比如SessionId
=1 的 Session 设置的超时时间是 2000ms,现在假设这个 Session 处理时长超过了 2000ms,那么它下次再次使用的时候就需要重新建立连接吗?这显然不妥,连接的建立是很耗时且耗费资源的,因此我们需要一个续期的功能,只要客户端做操作(CURD),那么服务端就会刷新该客户端对应 Session 的过期时间。如下图:
再想另外一种场景:我们客户端与服务端建立连接后如果不是手动销毁或者意外宕机等情况,我们是希望一直持有连接的,这样可以避免频繁创建、销毁连接这种影响性能的操作。那么客户端和服务端怎么彼此知道对方是否存活呢?单独靠需要操作(CRUD)的时候再去感知是不靠谱、也不优雅的,我们需要定时发 ping 请求去探活,也就是心跳机制。每次心跳正常都会更新此客户端对应 Session 的过期时间,这样来一直维活。
这里举个例子。假设我们声明一个 ZooKeeper 客户端,过期时间是 3000ms:
ZooKeeper client = new ZooKeeper("127.0.0.1:2181", 3000, null);
。
那么,客户端每隔一段时间就会进行心跳检测的,比如每隔 1000ms 检测一次,那么只要客户端和服务端都正常通信的话,客户端会每隔一秒给服务端发一个 ping 请求,服务端收到后会更新此请求对应的 Session 过期时间。
三、总结
Session 相关的原理部分我们到此结束了。其实本篇内容是很轻松愉快的,没有很复杂的逻辑在里面,最后我们做个总结。
我们先讲解了 HTTP Session 的由来以及概念,然后从 0 到 1 分析了 ZooKeeper Session 的用途以及一些原理。原理部分我们主要涉及到如下内容。
-
Session 是干什么的?什么时候需要创建 Session?我们有提到一个客户端的连接就对应一个 Session 的产生,Session 主要用于客户端与服务端之间通信。
-
采取 SessionId 来标记出这个 Session 的唯一性,但是 SessionId 是根据什么算法或者什么规则生成的呢?这个我们留到下一篇源码剖析当中去讲解。
-
我们还讲解到 Session 的特性:过期机制。讲解了 Session 过期时间的规则和注意事项,比如:最好设置成
tickTime
的整数倍,即使没设置成整数倍,那么实际过期时间也是倍数的。 -
我们还提及到防止 Session 过期时间太短,导致业务没执行完,客户端和服务端之间就断开连接的情况,我们会有两种刷新 Session 过期时间机制:正常业务操作(CRUD)以及心跳机制。
相信小伙伴有很多疑问,比如:SessionId 生成算法是什么?每隔 tickTime
去扫描过期的 Session 然后释放资源,那么 Session 以及过期时间是如何管理的?再比如:如果客户端宕机了,那么服务端怎么处理这个 Session?假设服务端宕机了,那么客户端该怎么办?这些我们都放到 Session 机制底层源码剖析里去讲解。