learn-tech/专栏/ZooKeeper源码分析与实战-完/09创建会话:避开日常开发的那些“坑”.md
2024-10-16 06:37:41 +08:00

98 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

因收到Google相关通知网站将会择期关闭。相关通知内容
09 创建会话:避开日常开发的那些“坑”
会话是 ZooKeeper 中最核心的概念之一。客户端与服务端的交互操作中都离不开会话的相关的操作。在前几节课中我们学到的临时节点、Watch 通知机制等都和客户端会话有密不可分的关系。比如一次会话关闭时,服务端会自动删除该会话所创建的临时节点,或者当客户端会话退出时,通过 Watch 监控机制就可以向订阅了该事件的客户端发送响应的通知。接下来我们就从会话的应用层使用,到 ZooKeeper 底层的实现原理,一步步学习会话的相关知识。
会话的创建
ZooKeeper 的工作方式一般是通过客户端向服务端发送请求而实现的。而在一个请求的发送过程中,首先,客户端要与服务端进行连接,而一个连接就是一个会话。在 ZooKeeper 中,一个会话可以看作是一个用于表示客户端与服务器端连接的数据结构 Session。而这个数据结构由三个部分组成分别是会话 IDsessionID、会话超时时间TimeOut、会话关闭状态isClosing如下图所示。
下面我们来分别介绍一下这三个部分:
会话 ID会话 ID 作为一个会话的标识符当我们创建一次会话的时候ZooKeeper 会自动为其分配一个唯一的 ID 编码。
会话超时时间:会话超时时间在我们之前的课程中也有涉及,一般来说,一个会话的超时时间就是指一次会话从发起后到被服务器关闭的时长。而设置会话超时时间后,服务器会参考设置的超时时间,最终计算一个服务端自己的超时时间。而这个超时时间则是最终真正用于 ZooKeeper 中服务端用户会话管理的超时时间。
会话关闭状态:会话关闭 isClosing 状态属性字段表示一个会话是否已经关闭。如果服务器检查到一个会话已经因为超时等原因失效时, ZooKeeper 会在该会话的 isClosing 属性值标记为关闭,再之后就不对该会话进行操作了。
会话状态
通过上面的学习,我们知道了 ZooKeeper 中一次会话的内部结构。下面我们就从系统运行的角度去分析,一次会话从创建到关闭的生命周期中都经历了哪些阶段。
上面是来自 ZooKeeper 官网的一张图片。该图片详细完整地描述了一次会话的完整生命周期。而通过该图片我们可以知道,在 ZooKeeper 服务的运行过程中会话会经历不同的状态变化。而这些状态包括正在连接CONNECTING、已经连接CONNECTIED、正在重新连接RECONNECTING、已经重新连接RECONNECTED、会话关闭CLOSE等。
当客户端开始创建一个与服务端的会话操作时,它的会话状态就会变成 CONNECTING之后客户端会根据服务器地址列表中的服务器 IP 地址分别尝试进行连接。如果遇到一个 IP 地址可以连接到服务器,那么客户端会话状态将变为 CONNECTIED。
而如果因为网络原因造成已经连接的客户端会话断开时,客户端会重新尝试连接服务端。而对应的客户端会话状态又变成 CONNECTING ,直到该会话连接到服务端最终又变成 CONNECTIED。
在 ZooKeeper 服务的整个运行过程中,会话状态经常会在 CONNECTING 与 CONNECTIED 之间进行切换。最后,当出现超时或者客户端主动退出程序等情况时,客户端会话状态则会变为 CLOSE 状态。
会话底层实现
一个会话可以看作是由四种不同的属性字段组成的一种数据结构。而在整个 ZooKeeper 服务的运行过程中,会话管理的本质就是围绕这个数据结构进行操作。
说到 ZooKeeper 中会话的底层实现,就不得不说 SessionTracker 类,该类可以说是 ZooKeeper 实现会话的核心类,用来实现会话管理和维护等相关操作。可以说,在 ZooKeeper 会话的整个生命周期中都离不开 SessionTracker 类的参与。
SessionTracker 是一个接口类型,其规定了 ZooKeeper 中会话管理的相关操作行为。而具体的实现方式则是通过 SessionTrackerImpl 类来完成的。
class SessionTrackerImpl implements SessionTracker{
ConcurrentHashMap<Long, SessionImpl> sessionsById
ConcurrentMap<Long, Integer> sessionsWithTimeout;
}
如上面的代码所示SessionTrackerImpl 类实现了 SessionTracker 接口。在其中有两个属性字段,分别是:
sessionsById用于根据会话 ID 来管理具体的会话实体。
sessionsWithTimeout根据不同的会话 ID 管理每个会话的超时时间。
而在 SessionTrackerImpl 类初始化的时候,首先会调用 initializeNextSession 方法来生成一个会话 ID ,该会话 ID 会作为一个唯一的标识符,在 ZooKeeper 服务之后的运行中用来标记一个特定的会话。
public static long initializeNextSession(long id) {
long nextSid;
nextSid = (Time.currentElapsedTime() << 24) >>> 8;
nextSid = nextSid | (id <<56);
return nextSid;
}
通过上面的代码我们可以了解到生成会话 ID 的过程首先以毫秒为单位获取系统的当前时间之后将该值通过位运算方式向左移动 24 再向右移动 8 最后根据服务器的 SID 进行或运算得到的最终结果就作为该会话的 ID 编码
有了会话 ID 和超时时间等信息后一个会话相关的所有数据就基本具备了也就完成了一个会话的创建工作接下来就是服务器接收到会话请求后的处理等操作了
会话异常
在平时的开发工作中我们最常遇到和处理的场景就是会话超时异常 ZooKeeper 会话的超时异常包括客户端 readtimeout 异常和服务器端 sessionTimeout 异常在我们平时的开发中要明确这两个异常的不同之处在于一个是发生在客户端而另一个是发生在服务端
而对于那些对 ZooKeeper 接触不深的开发人员来说他们常常踩坑的地方在于虽然设置了超时间但是在实际服务运行的时候 ZooKeeper 并没有按照设置的超时时间来管理会话
这是因为 ZooKeeper 实际起作用的超时时间是通过客户端和服务端协商决定 ZooKeeper 客户端在和服务端建立连接的时候会提交一个客户端设置的会话超时时间而该超时时间会和服务端设置的最大超时时间和最小超时时间进行比对如果正好在其允许的范围内则采用客户端的超时时间管理会话如果大于或者小于服务端设置的超时时间则采用服务端设置的值管理会话
结束
本课时我们从服务的运行角度分析了一次客户端会话从创建到关闭的整个过程在会话的整个生命周期中会经历正在连接已经连接关闭等不同状态的变化通过本节课的学习我们掌握了会话的底层实现原理进而在日常工作中面对会话的创建等问题更能得心应手在出现问题的时候能够利用我们所学的知识很快的排查问题
下面我们看一下这个问题ZooKeeper 生成会话 ID 编码的算法看起来比较复杂那么这个算法是否严谨呢
该算法实现上看起比较复杂但是其实并不完美其最主要的问题在于其算法的基础是通过Java 语言获取系统的当前时间作为算法的基本参数而在一些情况下在位运算的过程中会产生负数等问题不过这个问题在 3.4.6 版本后得到了解决这里请你注意在使用 ZooKeeper 的时候如果是在维护旧有系统时要考虑这个问题