4.计算机器码标识ID:
在initializeNextSession方法中,出现了一个id变量,这个变量就是生成的SID的值,而SID在部署的时候就是我们在myid中配置的值,一般是一个整数,假设此时的值为2,转为64位二进制表示:
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000010
此时发现高位几乎都是0,进行左移56位以后,得到值如下:
00000010 00000000 00000000 00000000 00000000 00000000 00000000 00000000
5.将前面第三步和第四步得到的结果进行 | 操作:
可以得到结果为:
00000010 01000001 10000011 11000100 01001101 11110111 00000000 00000000
这个时候我们可以得到一个单机中唯一的序列号ID,整个算法大概可以理解为,先通过高8位确定机器以后,后面的56位按照毫秒进行随机,可以看出来当前的算法!还是蛮严谨的,基本上看不出来什么明显的问题,但是其实也有问题的,其中我们可以看到,zk选择了当前机器时间内的毫秒作为基数,但是如果时间到了2022年4月8号以后, System . currentTimeMillis ()的值会是多少呢?
1. `Date d = newDate(2022-1900 f 3,8);`
2. `System. out. p rin tln ( Long. toBinaryString(d .getTime()));`
打印出来的结果为:
1. `0000000000000000000000011000000000000100110000010000010000000000`
接着我们左移24位以后会发现,这个时候的值依然是个负数,所以我们为了保证不会出现负数的情况,解决方案如下:
1. `publicstaticlong initializeNextSession(long id { ) {`
2. `long nextSid = 0;`
3. `nextSid = (System.currentTim eM illis() « 24) > » 8;`
4. `nextSid = nextSid | (id « 56);`
5. `return nextSid;`
6. `} }`
这样就可以避免生成的时候出现负数了
SessionTracker
SessionTracker是Zookeeper中的会话管理器,负责整个zk生命周期中会话的创建、管理和清理操作,而每一个会话在Sessiontracker内部都保留了三份,大体如下:
1.sessionsWithTimeout这是一个ConcurrentHashMap<long,integer style=“margin: 0px; padding: 0px; box-sizing: border-box;”>类型的数据结构,用来管理会话的超时时间,这个参数会被持久化到快照文件中去</long,integer>
2.sessionsById是一个HashMap<long,integer style=“margin: 0px; padding: 0px; box-sizing: border-box;”>类型的数据结构,用于根据sessionId来管理session实体</long,integer>
3.sessionsSets同样也是一个HashMap<long,integer style=“margin: 0px; padding: 0px; box-sizing: border-box;”>类型的数据结构,用来会话超时的时候进行归档,便于进行会话恢复和管理</long,integer>
会话创建
创建会话的过程,大体可以分为几个步骤,分别是处理ConnectRequest请求、创建会话、处理器链路处理和响应,在zk服务端中,首先是NIOServerCnxn来负责接受来自客户端的会话创建请求,并且进行反序列化工作,然后开始分配超时时间。分配完毕后,会开始创建sessionId,并且将其注册到SessionsById和sessionsWithTimeOut,进行激活,这个时候就可以考虑处理流转。
会话管理
Zookeeper中的会话管理主要是SesssionTracker负责的,内部使用了一个特殊的机制,称之为分桶策略,所谓分桶策略,其实是将类似的会话放在一个区块中进行管理,以便于zookeeper对会话进行不同区块的隔离以及同一区块的统一处理
从图中我们可以看到,所有的会话都分配在了不同的区块中,分配原则是每个会话的下个超时的时间点,ExpiractionTime是指最近一次可能过期的时间点,每一个会话的ExpiractionTime的计算方式如下:
ExpiractionTime = CurrentTime + SessionTimeout
但是不要忘记了,Zookeeper的Leader服务器在运行期间会定期检查是否超时,这个定期的时间间隔为ExpiractionInterval,单位是秒,默认情况下是tickTime的值,即2000毫秒进行一次检查,完整的ExpiractionTime的计算方式如下:
1. `ExpirationTime_= CurrentTime+ SessionTimeout;`
2. `ExpirationTime= (ExpirationTime_/ Expirationlnterval+ 1) x Expirationlnterval;`
会话激活
同样的,在整个zookeeper运行过程中,客户端会在超时时间内向服务端发送PING请求来保持时效性,俗称心跳检测,而服务端在接受到了客户端的心跳请求后需要再次激活会话状态,这个过程称之为TouchSession,流程如下:
1.检验会话是否已经被关闭,Leader会去检查会话是否被关闭,如果已经关闭,不会再去激活该会话
2.如果会话没有被关闭,则开始计算下一次的超时时间Expiration_New,而计算的过程则是使用上面的公式
3.计算完新的超时时间以后,会去获取会员原来的超时时间,并且根据时间来定位原来存放的区块
4.接着,从该区块中找到会话,进行会话迁移,放入新的Expiration_New对应的区块中,如图所示:
经过以上的步骤,基本已经完成了会话的激活,而每一次心跳的检测,则是进行了一次会话激活操作,在整个Zookeeper运行过程中,一般如下两个操作才会导致会话激活:
1.当客户端向服务端发送请求的时候,包括读写请求,都会主动触发一次会话激活
2.如果客户端在sessionTimeOut / 3时间范围内尚未和服务器之间进行通信,即没有发送任何请求,就会主动发起一个PING请求,去触发服务端的会话激活操作
除此之外,由于会话之间的激活是按照分桶策略进行保存的,因此我们可以利用此策略优化对于会话的超时检查,在Zookeeper中,会话超时检查也是由SessionTracker负责的,内部有一个线程专门进行会话的超时检查,只要依次的对每一个区块的会话进行检查,由于分桶是按照ExpriationInterval 的倍数来进行会话分布的,因此只要在这些时间点检查即可,这样可以减少检查的次数,并且批量清理会话,实现较高的效率。
会话清理
会话检查操作以后,当发现有超时的会话的时候,会进行会话清理操作,而Zookeeper中的会话清理操作,主要是以下几个步骤:
1.由于会话清理过程需要一定的时间,为了保证在清理的过程中,该会话不会再去接受和处理发来的请求,因此,在会话检查完毕后,SessionTracker会先将其会话的isClose标记为true,接着为了保证在进行会话关闭的过程中,在整个集群中都生效,Zookeeper使用了提交的方式,交给PreRequestProcessor处理器进行处理
2.在某个会话失效后,这个会话创建的相关临时节点列表都应该被删除,因此在删除会话之前,需要先找到与改会话相对应的临时节点列表,在Zookeeper的内存数据库中,会为每一个会话单独保存一份由该会话维护的临时节点集合,但是我们需要考虑一些特殊情况,例如在删除会话的时候,有没处理完毕的删除节点的请求,而这个被删除的节点刚好又是会话对应的临时节点,或者这个时候正在处理临时节点创建的请求,而且也是当前会话的请求。这个时候我们必须考虑处理方案,防止出现数据不一致的情况,而第一种情况,则是防止重复删除,我们只需要先把请求对应的节点删除,再去删除对应的列表即可,而第二种情况,我们也需要先执行添加节点的请求,保证节点不会出现删除遗漏即可。
3.当会话对应的临时节点列表找到后,Zookeeper会将列表中所有的节点变成删除节点的请求,并且丢给事物变更队列OutStandingChanges中,接着FinalRequestProcessor处理器会触发删除节点的操作,从内存数据库中删除。
4.当会话对应的临时节点被删除以后,就需要将会话从SessionTracker中移除了,主要从SessionById,sessionsWithTimeOut以及sessionsSets中将会话移除掉,当一切操作完成后,清理会话操作完成,这个时候将会关闭最终的连接NioServerCnxn。