ZooKeeper系统模型之会话创建。

Session

        Session是ZooKeeper中的会话实体,代表了一个客户端会话。其包含以下4个基本属性。

  • sessionID:会话ID,用来唯一标识一个会话,每次客户端创建新会话的时候,ZooKeeper都会为其分配一个全局唯一的sessionID。
  • TimeOut:会话超时时间。客户端在构造ZooKeeper实例的时候,会配置一个sessionTimeout参数用于指定会话的超时时间。ZooKeeper客户端向服务器发送这个超时时间后,服务器会根据自己的超时时间限制最终确定会话的超时时间。
  • TickTime:下次会话超时时间点。为了便于ZooKeeper对会话实行“分桶策略”管理,同时也是为了高效低耗的实现会话的超时检查与清理,ZooKeeper会为每个会话标记一个下次会话超时时间点。TickTime是一个13位的long型数据,其值接近于当前时间加上TimeOut,但不完全相等。
  • isClosing:该属性用于标记一个会话是否已经被关闭。通常当服务端检测到一个会话已经超时失效的时候,会将该会话的isClosing属性标记为“已关闭”,这样就能确保不再处理来自该会话的新请求了。

sessionID

        在上面我们也已经提到了,sessionID用来唯一标识一个会话,因此ZooKeeper必须保证sessionID的全局唯一性。在每次客户端向服务端发起“会话创建”请求时,服务端都会为其分配一个sessionID,现在我们就来看看sessionID究竟是如何生成的。

        在SessionTracker初始化的时候,会调用initializeNextSession方法来生成一个初始化的sessionID,之后在ZooKeeper的正常运行过程中,会在该sessionID的基础上为每个会话进行分配,其初始化算法如下:

        上面这个方法就是ZooKeeper初始化sessionID的算法,我们一起来深入的探究下其实现内幕。从上面的代码片段中,可以看出sessionID的生成大体可以分为以下5个步骤。

获取当前时间的毫秒表示

        我们假设System.currentTimeMillis()取出的值是1380895182327,其64位二进制表示是:

        其中阴影部分表示高24位,下划线部分表示低40位。

左移24位

        将步骤1中的数值左移24位,得到如下二进制表示的数值:

        从上面这个数值中,我们可以看到,之前的高24位已经被移出,同时低24位全部使用0进行了补齐。

右移8位

        再将步骤2中的数值右移8位,得到如下二进制表示的数值:

        从上面这个数值中,我们可以看到,高位添加了8个0。

添加机器标识:SID

        在initializeNextSession方法中,出现了一个id变量,该变量就是当前ZooKeeper服务器的SID值。SID是当时配置在myid文件中的值,该值通常是一个整数,例如1、2或3,这里我们为了便于表述,假设该值为2。整数2的64位二进制表示如下:

        可以发现其高56位都是0,将其左移56位后,可以得到如下二进制表示的数值:

将步骤3和步骤4得到的两个64位表示的数值进行“|”操作

|

       可以得到如下数值:

        通过以上5步,就完成了一个sessionID的初始化。因为ID是一个机器编号,比如1、2或3,因此经过上述算法计算之后,我们就可以得到一个单机唯一的序列号。简单的讲,可以将上述算法概括为:高8位确定了所在机器,后56位使用当前时间的毫秒表示进行随机。

        接下来,我们从几个算法细节上再来看下sessionID的初始化算法。

        为什么是左移24位?

我们以上述步骤1中使用的当前时间为例:

左移24位后是:

我们发现左移24位后,将高位的1移出了,剩下的最高位是0——这样做的目的是为了防止负数的出现。试想,如果是左移23位,那么左移的数值是:

显然,这时一个负数(-6862955700079820800),在此基础上即使进行右移8位操作,其数值最高位依然是“1”,因此之后就无法清晰地从sessionID中分辨出SID的值。

        该算法是否完美?

上述算法虽然看起来非常严谨,基本看不出什么明显的问题,但其实并不完美。上述算法的根基在步骤1,即能够获取到一个随机的,且在单机范围内不会出现重复的随机,我们将其称为“基数”——ZooKeeper选择了使用Java语言自带的当前时间的毫秒数来作为该基数。针对当前时间的毫秒表示,通常情况下没有什么问题,但如果假设到了2022年04月08日时,System.currentTimeMillis()的值会是什么呢?可以通过如下计算方式得到:

计算结果如下:

在这种情况下,即使左移24位,还是有问题,因为24位后还是负数,所以完美的解决方案是:

在上述代码中,我们使用阴影部分重点表示出了改进点,即使用无符号右移,而非有符号右移,这样可以避免高位数值对SID的干扰了。该缺陷在3.4.6版本的ZooKeeper中已经得到了修复。

SessionTracker

        SessionTracker是ZooKeeper服务端的会话管理器,负责会话的创建、管理和清理等工作。可以说,整个会话的生命周期都离不开SessionTracker的管理。每一个会话在SessionTracker内部都保留了三份,具体如下。

  • sessionById:这是一个HashMap<Long, SessionImpl>类型的数据结构,用于根据sessionID来管理Session实体。
  • sessionWithTimeout:这是一个ConcurrentHashMap<Long, Integer>类型的数据结构,用于根据sessionID来管理会话的超时时间。该数据结构和ZooKeeper内存数据库相连通,会被定期持久化到快照文件中去。
  • sessionSets:这是一个HashMap<Long, SessionSet>类型的数据结构,用于根据下次会话超时时间点来归档会话,便于进行会话管理和超时检查。

创建连接

        服务端对于客户端的“会话创建”请求的处理,大体可以分为四大步骤,分别是处理ConnectRequest请求、会话创建、处理器链路处理和会话响应。在ZooKeeper服务端,首先将会由NIOServerCnxn来负责接收到客户端的“会话创建”请求,并反序列化出ConnectRequest请求,然后根据ZooKeeper服务端的配置完成会话超时时间的协商。随后,SessionTracker将会为该会话分配一个sessionID,并将其注册到sessionsById和sessionsWithTimeout中去,同时进行会话的激活。之后,该“会话请求”还会再ZooKeeper服务端的各个请求处理器之间进行顺序流转,最终完成会话的创建。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值