1.zookeeper介绍

              定义:zookeeper是分布式协调服务框架,开源,为分布式系统提供一致性服务

              使用了zookeeper的开源项目:

                     kafka:zookeeper为kafka提供broker和topic的注册以及多个partition的负载均衡功能

                     Hbase:ZooKeeper 为 Hbase 提供确保整个集群只有一个 Master 以及保存和提供 regionserver 状态信息(是否在线)等功能

                     Hadoop : ZooKeeper 为 Namenode 提供高可用支持。

                     Dubbo:阿里巴巴集团开源的分布式服务框架,它使用 ZooKeeper 来作为其命名服务,维护全局的服务地址列表。

              zookeeper三种运行模式:单机运行、伪集群模式、集群模式

                     单机模式:适用于开发测试环境,一是因为服务器资源没那么多,二是平时开发调试无需较好的稳定性

                     集群模式:由一组机器组成,一般三台以上就可以组成一个zookeeper集群了。组成zookeeper集群的每台机器都会在内存中维护当前的服务器状态,并且每台机器之间都会互相保持通信

                     伪集群模式:所有服务器都部署在一台机器上,一台机器上启动多个不同端口的zookeeper服务实例,从而以集群的方式对外提供服务

     2.CAP和BASE理论

              分布式系统必然存在的问题,因为分区容忍性(分布式系统在面对部分节点无法通信的情况下,仍然可继续运行的特性)的存在,就必须在系统可用性和数据一致性中做出权衡,便是CAP定理

              保证可用性,是Eureka的处理方式;保证数据一致性,是Zookeeper的处理方式

              CAP理论中,分区容忍性(P)是一定要满足的,只能从AP(可用性)和CP(一致性)中寻找平衡

              如何平衡,便出现了BASE理论:即使无法达到强一致性,但分布式可根据自己的业务特点,采用适当的方式来使系统达到最终的一致性。

              BASE理论:

                     Basically Avaliable 基本可用

                            分布式系统出现故障时,允许损失部分可用性,即保证核心可用。例如电商系统为了应对访问量激增,部分用户可能会被引导至降级界面,服务层只在该页面提供降级服务

                     Soft state 软状态

                            指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。分布式存储中一般一份数据至少会有多个副本,允许不同节点副本同步的延时就是软状态的体现

                     Eventually consistent 最终一致性

                            指系统中所有数据副本经过一段时间后,最终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况

                     总结就是:平时系统要求是基本可用,运行有可容忍的延迟状态,但是无论经过一段时间后的延迟后,系统必须达成数据是一致的

                     ACID是传统数据库常用的设计理念,追求强一致性模型。BASE支持的是大型分布式系统,通过牺牲强一致性获得高可用性

                     不论是CAP理论还是BASE理论,都是需要算法实现的,常见算法有2PC、3PC、ZAB、Raft、Paxos。解决的问题全都是:分布式环境下,如何让系统更高可用,而且数据量最终达到一致

     3.Zookeeper特点

              集群:Zookeeper是一个领导者(Leader),多个跟随者(Follower)组成的集群

              高可用性:集群中只要有半数以上节点存活,zookeeper集群就能正常服务

              全局数据一致:每个Server保存一份相同的数据副本,Client无论连接到哪个Server,数据都是一致的

              更新请求顺序进行:来自同一个Client的更新请求按其发送顺序依次执行

              数据更新原子性:一次数据更新要么成功,要么失败

              实时数据:在一定时间范围内,client能读取最新数据

              从设计模式角度来看,zk是一个基于观察设计模式的框架,负责管理和存储大家都关心的数据,然后接受观察者的注册,数据发生变化,zk会通知在zk上注册的观察者做出反应

              zookeeper是一个分布式协调系统,满足CP(一致性和分区容忍性),与springcloud中的eureka满足AP(可用性和分区容忍性)不一样

     4.一致性协议之ZAB

              zookeeer在解决分布式数据一致性问题时并未直接使用了paxos,而是专门定制了一致性协议叫做ZAB(zookeeper automic broadcast)原子广播协议,该协议能很好的支持崩溃恢复

              zab中的三个角色:

                    leader-领导者:集群中唯一的写请求处理者,能够发起投票(投票也是为了写请求)

                     follower-跟随者:能够接收客户端请求,如果是读请求则可以自己处理,如果是写请求则要转发给leader.在选举过程中会参与投票,有选举权和被选举权

                     observer:就是没有选举权和被选举权的follower

                     ZAB协议中对上述三个角色还有两种模式的定义,分别为消息广播和崩溃恢复      

              ZXID和myid

                     zk采用全局递增的事务id来标识,所有proposal(提议)在提出时候加上了zookeeper transaction id。ZXID是64位的long类型,这是保证事务一致性的关键。ZXID中高32位表示纪元epoch,

                            低32位表示事务表示id,zxid越大,存储数据越新。

                     每个leader都会具有不同的epoch值,表示一个纪元/朝代,用来表示Leader周期。每个新的选举开启时都会产生一个新的eopch,从1开始,每次选出新的leader,epoch递增1,并将该值更新到

                            所有的zkserver的xid的epoch

                     xid是一个依次递增的事务编号,数值越大说明数据越新,可简单理解为递增的事务id,每次epoch变化,都将低32位的序号重置,这样可保证zxid的全局递增性质

                     每个zookeeper服务器,都需要在数据文件夹下创建一个名为myid的文件,该文件包含整个zookeeper集群唯一的id(整数)。在配置文件中,server.后面的数据即为myid

              历史队列

                     每一个follower节点都会有1个FIFO(先进先出)的队列 用来存放收到的事务请求,保证执行事务的顺序,所以,

                            可靠提交由ZAB的事务一致性协议保证

                            全局有序由TCP协议保证

                            因果有序由follower的历史队列保证

              消息广播模式

                     ZAB协议有两种模式,消息广播模式和崩溃恢复模式

                     本质上就是ZAB如何处理写请求的,只有leader可以处理写请求,但是follower和observer也需要同步数据。

                     第一步是leader将写请求广播出去,leader询问follower是否同意更新,如果超过半数以上的同意就进行follower和observer的更新。消息广播机制通过以下步骤来保证事务的顺序一致性的

                            1.leader从客户端收到写请求

                            2.leader生成一个新的事务,并为此事务生成一个ZXID

                            3.leader将这个事务发送给所有的follower节点,将带有zxid的消息作为一个提案分发给所有follower

                            4.follower节点将收到的事务请求加入到历史队列中,当follower收到proposal,先将proposal写到硬盘,写入成功后再向leader返回一个ACK

                            5.当leader收到大多数follower的ACK,leader会向follower发送commit请求(leader自身也要提交这个事务)

                            6.当follower收到commit请求,会判断该事务的ZXID是否比历史队列中的任何事务的ZXID都要小,如果是则提交事务,如果不是则等待比他更小的事务commit(保证顺序性)

                            7.leader将处理结果返回客户端

                     过半编写策略:leader节点收到写请求后,这个leader会将写请求广播给各个server,各个server会将写请求加入到历史队列,并向leader发送ack消息,当leader收到一半以上的ACK消息后,说明该写操作可执行,leader会向各个server发送commit消息,server收到消息之后执行commit操作。但是需要注意以下几点:

                            leader并不需要得到observe的ACK,即observer无投票权

                            leader不需要得到所有的server的同意,只需要得到半数以上的server的同意即可,同时leader本身对自己也有一个ack

                            observer虽然没有投票权,但仍需要同步leader的数据,以确保在处理读请求时尽可能返回新的数据

                     follower和observer接收写请求,不能直接处理,需要将请求转发给leader处理,leader处理写请求,通过消息广播模式,最终所有的zkServer都会执行写操作,才能保证数据一致

                     对于读请求,leader、follower、observer都可处理,从本地内存中读取数据返回即可。由于处理读请求不需要服务器之间的交互,因此follower/observer越多,整体可处理的读请求量越大,读性能才越好。

              崩溃恢复模式

                     可分为4个阶段:选举、发现、同步、广播

                     选举阶段:leader崩溃后,集群进入选举阶段,开始选举出潜在的准leader,然后进入下一阶段

                     发现阶段:用于在从节点中发现最新的ZXID和事务日志。

                     同步阶段:利用leader前一阶段获得的最新提议历史,同步给集群中所有follower,只有当半数follower同步成功,准leader才会称为真正的leader.之后leader只会接收zxid比自己lastzxid大的提议

                     广播阶段:集群恢复到广播模式,开始接收客户端的写请求

              脑裂问题:

                     假设集群中有2个zookeeper节点,并且都知道选出一个master,当两节点之间通信没问题的时候,会达成公式,选出1个作为master;但是如果两节点之间通信出了问题,两节点都会觉得现在没有master,所以都会把选为maste,于是集群中便有了两个master.

                     ZAB解决脑裂问题:要求集群内的节点数量为2n+1,当网络分裂后,始终有1个集群的节点数量过半数,而另一个集群节点数量小于半数,因为选master需要过半数同意,所以任何情况下,都不可能出现大于一个Leader的情况。

                     因此,有了过半机制,对于一个zookeeper集群,要么没有leader,要么只有一个leader,就避免了脑裂情况。

     5.Zookeeper选举机制 todo

              leader选举分为两个不同的阶段,第一个是提到的leader宕机需要重新选举;第二个是zookeeper启动时需要进行系统的leader初始化选举。下面是zkServer的几种状态

                     LOOKING:不确定leader状态,该状态下的服务器认为集群中没有leader,会进行leader选举

                     FOLLOWING:跟随者状态,表明当前服务器是follower,并清楚leader是谁

                     LEADING:领导者状态,表明当前服务器角色是leader,会维护和follower之间的心跳

                     OBSERVING:观察者状态,表明当前服务器角色是observer,与follower唯一的区别在于不参与选举,也不参与集群写操作时的投票

              5.1 初始化leader选举

                     假设集群中有3台服务器,选举leader需要超过半数服务器同意,也就是2台,假设3台服务器id分别为1,2,3,初始化leader选举过程如下:

                     服务器1启动,发起一次选举,会将票投给自己,投票内容为(myid,zxid),因为初始化所以zxid都为0,此时server1发出的投票为(1,0),即myid为1,zxid为0,此时有服务器1票数一票,不够半数以上,选举无法完成,服务器1状态保持为LOOKING

                     服务器2启动,再发起一次选举,服务器2也会将票投给自己,并将投票广播出去(server2也会,只是那是没有其他服务器),server1收到server2的投票信息后会将投票信息和自己进行比较,首先它会比较ZXID,ZXID大的优先为LEADER,如果相同则比较myID,myid大的优先作为LEADER,所以此时server1发现server2更适合做leader,它就会将自己的投票信息更改为(2,0)然后再广播出去,之后server2收到发现和自己的一样无需做更改。此时服务器1票数0票,服务器2票数2票,投票已超过半数,确定服务器2为leader,服务器1更改状态为FOLLOWING,服务器2更改状态为LEADING.

                     服务器3启动,发起一次选举,此时服务器1、2已经不是LOOGING状态,它会直接以FOLLOWING状态进入集群。

              5.2 运行时选举LEADER

                     运行时,如果leader节点崩溃了会走崩溃恢复模式,新leader选出前会暂停对外服务,大致可分为以下4个阶段:选举、发现、同步、广播,具体流程如下:

                     leader挂掉后,剩余2台服务器状态从following变为looking,每个server发出一次投票,并都投给自己,投票内容为(myid,zxid),此时的zxid可能不为0

                     收集来自剩余存活服务器的投票信息

                     处理投票,逻辑为:优先比较ZXID,其次比较MyId

                     统计投票,只要半数的机器接收到同样的投票信息,就可以确定leader

                     改变服务器状态looking为following或leading

                     然后依次进入发现、同步、广播阶段

                     例如有3台服务器,server1,server2,server3,server2作为leader崩溃了,随后server1和server3分别给自己投票(1,99),(3,95),然后广播给其他server,server1和server3分别收到了来自对方的投票信息,随后会比较自己的投票和对方的投票信息,server1收到server3的发现没自己适合的故不变自己投票,server3收到server1的投票,发现比自己的投票更合适,于是便将自己的投票改为(1,99)广播出去,最后server1发现自己的投票超过半数,便将自己设置为leader,server3也随之变为follower

     6.Zookeeper数据模型

              zk data model采用层次化的多叉树形结构,每个节点都可以存储数据,数据可以是数字、字符串或二进制序列。而且,每个节点还可以拥有N个子节点,最顶层节点采用/表示

              每个数据节点在zk中称为zNode,是zk中数据的最小单元。每个zNode都有唯一的路径标识,由于zk主要是用来协调服务的,并非用来存储业务数据的,此特性使得zk不能用于存放大量的数据,每个节点数据存放上限为1M

              zNode可自由创建、删除,在一个znode下新建、删除znode,唯一的不同在于znode下是可以存储数据的,默认有四种类型的znode

                     持久化目录节点,PERSISTENT,客户端与zookeeper断开连接后,该节点依旧存在

                     持久化顺序编号目录节点,PERSISTENT_SEQUENTIAL,客户端与zookeeper断开连接后,节点依然存在,只是zookeeper给该节点名称进行顺序编号

                     临时目录节点,EPHEMERAL,客户端与zookeeper断开连接后,删除节点

                     临时顺序编号目录节点,EPHEMERAL_SEQUENTIAL,客户端与zookeeper断开连接后,删除节点,只是zookeeper给该节点名称进行顺序编号

              zk客户端中可以使用get命令查看znode的内容和状态信息

              每个znode的状态信息详细说明:

                     cZxid:节点创建时的事务id

                     ctime: 节点创建时的毫秒数,从1970开始

                     mZxid:节点最后更新的事务id

                     mtime:节点最后更新的毫秒数

                     pZxid:节点最后更新子节点列表的事务id

                     cversion:节点的子节点变化号,节点的子节点修改次数,每次节点变化时值加1

                     dataVersion:节点数据变化号,节点创建时为0,每次更新内容(无论内容是否发生变化),值加1

                     aclVersion:节点访问控制列表版本号,标识该节点ACL信息变更次数

                     ephemeralOwner:如果是临时节点,则是节点拥有者的sessionId。非临时节点,则为0

                     dataLength:节点数据长度

                     numChildren:节点的子节点数量

     7.zookeeper监听通知机制

              watcher监听机制是zk的重要特性,基于zk上创建的节点,可对这些节点创建监听事件,比如可以监听节点数据变更、节点删除、子节点状态变更等事件,通过此机制,可基于zk实现分布式锁、集群管理等功能,有点类似于订阅的方式,即客户端向服务器注册指定的watcher,当服务端符合了watcher的某些事件或要求则会向客户端发送事件通知,客户端收到通知后找到自己定义的watcher然后执行相应的回调方法

              当客户端在zk上的某个节点绑定监听事件后,如果事件触发,服务端会通过回调函数的方式通知客户端,但是客户端只会收到一次通知。如果后续此节点再次发生变化,那么之前设置watcher的客户端不会再次收到消息(watcher是一次性操作),可以设置循环监听达到永久监听效果

              zk的watcher机制,可分为三个过程

                     客户端注册watcher,注册方式有三种:getData,exists,getChildren

                     服务器处理watcher

                     客户端回调watcher服务端

              监听通知机制流程如下:

                     首先有main线程

                     main线程中创建zkClient,此时会创建两个线程,一个负责网络通信(connect),一个负责监听(listener)

                     通过conn线程将注册的监听事件发送给zk

                     在zk注册监听器列表中将注册的监听事件添加到列表中

                     zk监听到有数据或路径变化,会将消息发送给监听线程

                     listener线程内部调用了process()方法

     8.zookeeper会话(session)

              session可看作是zk客户端和服务器之间的一个TCP长连接,客户端和服务端之间的任何交互操作都和session息息相关,其中包含zookeeper的临时节点的声明周期、客户端请求执行以及watch机制等

              client端连接server默认的2181端口,也就是session会话

              从全局的会话状态变化到创建会话再到会话管理三个方面来看zk如何处理会话相关操作

              8.1 会话状态

                     connection 连接中:session一旦建立,便是connection状态,时间很短

                     connected 已连接:连接成功之后的状态

                     closes 已关闭,发现session过期,一般由于网络故障客户端重连失败,服务器宕机或客户端主动断开

                     客户端与服务端创建1个会话,此时客户端需要提供一个服务端地址列表,host:port,host1:port1,host2:port2,一般由地址管理器(hostProvide)管理,然后根据地址创建zookeeper对象。此时客户端对象的状态为CONNECTING,同时客户端会根据上述的地址列表,按照顺序的方式获取ip来尝试建立网络连接,直到成功连接上服务器,此时客户端的状态变为connected。在zk服务端提供服务过程中,可能遇到网络波动,导致客户端和服务端断开连接,此时客户端会和服务端再次尝试建立连接,状态为connection,再次连接成功后,状态会变为connected。只要在zk运行期间,客户端·的状态总是能保持在connectiong和connected的。当建立连接过程中,如果连接超时,权限检查失败或者在建立连接过程中,主动退出连接操作,客户端状态会变为close状态。

              8.2 会话ID的生成

                     一个会话必须包含以下属性

                     SessionId:会话id,会话的唯一标识,每次客户端建立连接时,zk服务端会给其分配给一个全局唯一的sessionId。在zookeeper中,无论那台服务器为客户端分配的sessionId,都务必保证全局唯一。

                     TimeOut:一次会话的超时时间,客户端在构造zookeeper实例的时候,会配置一个sessionTimeOut参数用于指定会话的超时的时间。zookeeper服务端按照客户端发来的timeOut参数计算并确定超时的时间。如果因服务器压力过大、网络故障或客户端主动断开连接等各种原因导致客户端断开连接时,只要在超时规定的时间内能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效

                     ExpirationTime:timeOut是一个相对的时间,expirationTime是一个绝对过期时间,比较通用的计算方法是 绝对过期时间=当前时间+timeOut,这样计算最准确,但zk并非如此计算

                     TickTime:下次会话超时的时间点,为了便于zk对会话实行分桶策略管理,同时也是为了高效低耗地实现会话的超时检查和清理,zk会为每个会话标记一个下次会话超时节点。ticktime是一个13位的long类型数值,一般这种情况下这个值接近timeout,但并不相等

                     iscloseing:用来标记当前会话是否处于已被关闭的状态,如果服务端检测到当前会话的超时时间已经到了,就会将iscloseing属性标记为关闭,这样即便以后再有这个会话的请求访问也不会处理

                    

                     sessionId作为全局唯一ID,zookeeper如何在集群环境下保证sessionID全局唯一性的

                            sessionTracker初始化时,会调用initializeNextSession生成sessionId,算法步骤大致如下:

                                   首先获取当前时间的毫秒表示

                                   其次将毫秒表示的64位二进制表示向左移24位,再向右移8位

                                   计算机器码表示ID,将当前zkServer的myid配置的值,用64位二进制表示,向左移动56位

                                   将最终得到的毫秒的64位二进制结果和最终的计算机标识码结果进行|操作,便可得到集群中唯一的序列ID

                            算法理解:先通过高8位获取zkServer所在机器后,后面56位按照毫秒进行随机

                            但是选择当前时间毫秒数作为基数,左移24位可能会变成负数,因此可以在向右移8位时,使用>>>无符号右移(当目标是负数时,移位时忽略符号位,空位以0补齐,确保结果永远是正数)

              8.3 SessionTracker与ClientCnxn

                     sessionTracker是zk中的会话管理器,负责整个zk声明周期中会话的创建、管理和清理操作,每一个会话在sessionTracker中有三部分数据结构:

                            sessionById:hashmap数据结果,用来根据sessionId管理session实体

                            sessionWithTimeOut:concurrentHashMap数据结构,用来管理session超时时间,此参数会被持久化到快照文件中

                            sessionSets:hashmap数据结构,用于会话超时数据归档,便于进行会话恢复和管理

                     clientCnxn是zk客户端的核心工作类,负责维护客户端和网络端的网络连接并进行一系列网络通信

                            clientcnxn内部包含两个线程:

                                   SendThread:I/O线程,负责zk客户端和服务端之间的网络I/O通信,维护了客户端和服务端之间的会话声明周期,通过一定的周期频率向服务端发送一个Ping包来实现心跳检测。还负责客户端所有的请求发送和响应接收,将上层客户端API操作转换成响应的协议请求发送到服务端,并完成同步调用的返回和异步调用的回调。还负责将来自服务端的事件交给eventThread处理

                                   EventThread:事件线程,负责对服务端事件进行处理。负责客户端的事件处理,并触发客户端注册的watcher监听。eventThread中有1个队列,临时存放需要被触发的object,包括客户端注册的watcher和异步接口中注册的回调器asynccallback。eventThread 会不断从waitevents队列中取出Object,识别具体类型,分别调用process(Watcher)和processResult(AsyncCallBack)来实现事件的触发和回调

                            clientcnxn中包含两个核心队列:outgoingQueue和pendingQueue,分别代表客户端的服务请求发送队列和服务端响应的等待队列

                                   outingQueue专门存储客户端需要发送到服务端的packet集合

                                   pengingQueue专门存储客户端已经发送到服务端的,但是需要等待服务端响应的packet集合

                            clientcnxnSocket底层是socket通信,定义了socket通信的接口,zk中默认实现为clientcnxnSocketNio,主要负责对请求的发送和响应的接收过程

                                   请求发送:TCP连接正常情况下,从outingQueue队列中按照先进先出顺序选择可发送packet对象,同时生成客户端请求序号XID并将其设置到packet请求头中,序列化后发送。请求发送完毕后,立即将packet对象保存到pengindQueue中,以便等待服务端响应返回后进行响应的处理

                                   响应接收:客户端接收到服务端响应后,根据不同的客户端请求类型,进行相应的处理。

                                          如果检测到当前客户端还未初始化,则说明当前客户端和服务端正在进行连接,就将收到的bytebuffer序列转换成connectresponse对象

                                          如果客户端处于正常会话周期中,并且接收到的服务端响应是一个事件,那么就将接收到的bytebuffer序列化成watchevent对象,并将该事件放入待处理队列中

                                          如果是一个常规的请求响应,就会从pendingQueue中取出一个packet,按照XID顺序将接收到的bytebuffer序列化成响应的response对象

              8.4 会话创建

                     流程如下:

                     1.client从服务器列表地址中随机选取一个地址,委托给 clientcnxnsocket 去创建和zk之间的长连接

                     2.sendthread会负责根据当前客户端的设置,构造出一个connectrequest请求,该请求代表了客户端试图与服务器建立一个会话。同时,zk 客户端还会进一步将请求包装成网络IO的packet对象,放入请求发送队列outgoindQueue中

                     3.当客户端请求准备完毕后,clientcnxnsocket从outgoingqueue中取出packet对象,将其序列化成bytebuffer后,向服务器进行发送

                     4.服务端的sessionTracker为该会话分配一个sessionId,并发送响应

                     5.client收到响应后,先判断客户端是否初始化,如果没有,则认为一定是创建请求会话的响应,直接交由readConnectResult方法处理请求

                     6.clientcnxnsocket对收到的响应进行反序列化,得到connectresponse对象,并从中获取到zk服务端分贝的会话sessionId

                     7.连接成功后,一方面通知sendthread线程,进一步对客户端进行会话参数设置,包括readtimeout和connecttimeout等,并更新客户端状态;另一方面,需要通知地址管理器hostprovider当前成功连接的服务器地址

                     8.为了能够让上层应用感知到会话的存在,sendthread会生成一个事件syncconnected-none,代表客户端与服务器会话创建成功,并将该事件传递给eventthread线程

                     9.eventthread收到事件后,会从clientwatchermanager管理器中查询出对应的watcher,针对synconnected-none事件,那么就直接找出存储的watcher,然后将其放到eventthread的waitingevents队列中

                     10.eventthread不断地从waitingevents队列中取出待处理的watcher对象,然后调用该对象的process方法,以达到触发watcher的目的

              8.5 会话超时管理

                     session由zk服务端管理,一个服务端可以为多个客户端服务,也就是有多个session.多个session是通过分桶机制来管理的一个手段。zk服务器端会维护着一个个桶,然后将sessio分发到一个个桶中。区分的维度就是expirationTime。

                     因为zk的服务端在运行期间定时地对会话进行超时检测,如果不对session进行维护,那么在检测时就会遍历所有的session,所以才以超时时间为维度来存放session,这样检测时,只需扫描对应的桶就可以。

                     但是每个session的超时时间都是一个很分散的值,会造成有多少个session就有多少个桶,这样不太合理,因为zk的expirationtime最终是expirationinterval的倍数,而expirationinterval是zk服务端定期检查过期session的频率,默认为2000毫秒。所以每个session的expirationtime最后都是一个近似值,是expirationinterval的倍数,这样,zk在扫描时只需要扫描一个桶即可.

                     采用分桶策略可降低某一时刻大量的会话同时超时的可能性,从而减少服务器的负担

              8.6 会话激活

                     客户端与服务端完成连接之后生成过期时间,这个值并非一成不变的,会随着客户端和服务端的交互来更新。过期时间的更新,伴随着session在桶上的迁移。过期时间计算的过程就是8.4的计算公式,计算完新的超时时间后,就可以放在相应桶位置上。激活方式有:

                            客户端每向服务端发送请求,包括读和写,都会触发一次激活,因为这预示客户端处于活跃状态

                            如果客户端一直没有读写请求,那么它在timeOut的三分之一时间都没发送过请求,那么客户端会发送一次PING,来触发session的激活。如果客户端直接断开连接的话,那么timeOut结束后,就会被服务端扫描后然后进行清除了。

                     除此之外,会话之间的激活是按照分桶策略保存的,因此可利用此策略来优化对于会话的超时检查,在zookeeper中,会话超时检查也是由sessiontracker来负责的,内部有1个线程专门进行会话的超时检查,只要依次的对每个区块的会话进行检查,由于分桶是按照expriationlnterval的倍数进行会话分布的,因此只要在这些时间点检查,可以减少检查的次数,并且批量清理会话,效率较高

              8.7 会话清理

                     会话检查操作之后,如果发现有超时的会话,会进行清理。主要操作如下

                            1.会话清理需要时间,为了保证清理过程中不接收和处理其他发来的请求,因此会话检查完毕之后,sessiontracker会将isclose设置为true,这样即便收到新的会话也无法处理

                            2.发起会话关闭请求给preprequestprocessor,使其在整个zk集群中生效

                            3.收集需要清理的临时节点-通过sessionwithtimeout和分桶策略找到超时的会话

                            4.找到会话对应的临时节点列表,zk将列表中的节点变成删除节点的请求,丢给事务变更队列outstangingchanges,随后FinalRequestProcessor处理器会触发删除节点的操作,从内存数据库中删除

                            5.会话对应临时节点删除后,就需要将会话从sessiontracker中移除了,主要从sessionbyid、sessionswithwimeout、sessionsets中将会话移除,一切操作完成后,会话清理操作完成,此时会关闭最终的连接nioservercnxn

              8.8 会话重连

                     zk运行过程中,可能会出现会话断开重连的情况,此时客户端会从连接列表中按照顺序的方式重新建立连接,直到连接一台服务器为止。此时可能会有两种状态:一种是正常连接connected,这种情况是zk客户端在超时时间内连接上了服务端,此时sessionid不变;如果在超时时间之后才连接上服务端,此时视为非法会话,状态为EXPIRED

                     重连之前,可能会因其他原因导致的断开连接,即CONNECTED_LESS,会抛出connectionlessexception异常,可能会出现两种情况

                            会话失效:SESSION_EXPIRED ,一般发生在connection_less状态期间,在超时时间之后,才与服务端建立连接,此时服务端会通知客户端会话失效,需要重新创建会话进行新的操作

                            会话转移:SESSION_MOVED,也是在重连过程中发生的一种情况,比如会话在服务端A上,断开重连之后,连接到了服务端B上。可能会导致一个请求在多个不同服务端执行多次。zk3.2版本开始,有了此概念,封装了sessionmovedexception,在处理客户端请求时,会检查请求会话是否是当前服务端的,如果不是,直接抛出sessionmovedexception异常。只是此时客户端已断开连接,无法接收服务端抛出的异常

     9.zookeeper分布式锁 

              利用zk的临时顺序节点,可轻松实现分布式锁。分布式锁也是zookeepre设计的初衷

              9.1 获取锁

                     首先,在zk中创建一个持久节点parentlock,当第一个客户端想要获取锁时,在此节点下创建一个临时顺序节点Lock1

                     之后,client1查找parentlocl下所有的临时顺序节点并排序,并判断自己创建的节点Lock1是否是顺序最靠前的一个,如果是第一个节点,则成功获得锁

                     这是,有另一个客户端client2来获取锁,则在parentlock下再创建一个临时顺序节点lock2,client2查找parentlock下的所有临时节点列表,并判断自己创建的临时顺序节点Lock2是否是第一个,如果不是最小的,则client2向比他考前的节点Lock1注册watcher,用于监听Lock1是否存在,同时也表示client2抢锁失败,进入等待状态

                    

                     之后如果又有一个client3来获取锁,同样再parentlock下再创建一个临时顺序节点Lock3,并也在parentlock查找所有临时节点列表,同样判断自己创建的临时顺序节点Lock3是否是第一个,如果不是,则向lock2注册watcher,用于监听lock2是否存在,进入等待状态

                     最终,client1获取了锁,client2监听Lock1,client3监听lock2,行程一个等待队列。

              9.2 释放锁

                     分两种情况:

                     1.任务完成,客户端显式释放

                            任务完成,客户端显式调用删除节点lock1的指令

                     2.任务执行过程中,客户端崩溃

                            得到锁的client1在执行过程中,客户端崩溃,则会断开与zk服务端的连接,根据临时节点特性,便会自动删除

                            同时client2也在一直监听lock1,当lock1节点删除,client2会获得通知,开始检查确认自己的临时节点lock2是否是parentlock下的第一个节点,如果最小,则client2获得锁

                            同理,如果client2任务完成或执行过程中节点崩溃而删除节点Lock2,那么client2会收到通知,最终client3获得了锁

              9.3 zk分布式锁和redis分布式锁的比较

                     zk分布式锁优点:有封装锁的框架,容器实现;有等待锁的队列,大大提升抢锁效率

                     zk分布式锁缺点:添加和删除节点性能较低

                     redis分布式锁优点:set和del指令性能较高

                     redis分布式锁缺点:实现复杂,需要考虑超时、原子性、误删等情况;没有等待锁的队列,只能在客户端自旋来等锁,效率较低

                     共同点:都支持可重入锁。可重入指的是某个线程已经获得了锁,可再次获得锁而不会出现死锁

     10.zookeeper几个应用场景

              10.1 数据发布/订阅

                     当某些数据由几个机器共享,且这些数据经常变化而且数据量还小的时候,这些数据适合保存到zk中

                            数据存储:将数据存储到zk上的一个数据节点

                            数据获取:应用在启动初始化节点从zookeeper数据节点获取数据,并在该节点注册一个watcher

                            数据变更:变更数据时会更新zk对应节点数据,zk会将数据变更通知发到各客户端,客户端收到通知后重新读取变更后的数据即可

              10.2 统一配置管理

                     本质上,统一配置管理和数据发布/订阅是一致的

                     分布式环境下,配置文件的同步由zk实现

                            1.将配置文件写入zk的一个zNode

                            2.各个客户端监听这个zNode

                            3.一旦zNode发生变更,zk将通知各个客户端服务

              10.3 统一集群管理

                     当需要连接中集群中有多少机器工作,相对集群中每台机器运行状态进行数据采集,对集群中机器进行上下线操作等等

                     例如:集群机器监控,通常用于对那种机器状态、机器在线效率较高的场景,能否快速对集群中机器变化做出响应。这样场景中,都会有监控系统,实时检测集群机器是否存活。过去做法通常是:监控系统通过某种手段(例如ping)定时检测某个机器,或者每个机器自己定时向监控系统汇报还存活。这种做法可行,但是存在两个比较明显的问题:集群中有机器变动时,牵连修改的东西较多;有一定的延时;

                     利用zk特性,可实现另一种集群机器存活性监控系统

                            客户端在某个节点上注册一个watcher,如果该节点的子节点变化了,会通知该客户端

                            创建EPHEMERAL类型的节点,一旦客户端和服务端的会话结束或过期,那么该节点会删除消失

              10.4 负载均衡

                     多个相同jar包在不同服务器开启相同的服务,可通过nginx在服务端做负载均衡的配置,也可通过zookeeper在客户端进行负载均衡配置

                            zk负载均衡实现步骤:1.多个服务注册;2.客户端获取中间件地址集合;3.从集合中随机选取一个服务执行服务

                     zookeeper和nginx负载均衡的区别:

                            zookeepre不存在单点问题,zab机制保障单点故障可重新选举一个Leader只负责服务的注册和发现,不负责转发,减少一次数据交换(消费方与服务方直接通信),需要自己实现响应的负载均衡算法

                            nginx存在单点问题,单点负载高数据量大,需要通过Keepalived+vcs备机实现高可用。每次负载,都充当一次中间人转发角色,增加网络负载量(消费方和服务方直接通信),自带负载均衡算法

              10.5 命名服务

                     命名服务是指通过指定的名字来获取资源或服务的地址,利用zk创建一个全局唯一的路径,此路径可指向集群中某个具体的服务器,提供服务的地址或者一个远程对象等

                     dubbo中使用zookeeper来作为其命名服务,维护全局的服务地址列表。

                     在dubbo实现中,服务提供者在启动的时候,向zookeeper的指定节点目录写下自己的URL地址,便完成了服务的发布;服务消费者启动时,订阅'服务提供者写URL地址的目录'。写下自己的URL地址

                     向zookeeper上注册的地址都是临时节点,以便能否保证服务提供者和消费者能自动感应资源的变化

                     dubbo还有针对服务粒度的监控,方法是订阅指定目录下所有提供者和消费者的信息

              10.6 分布式锁和选举也是zookeeper常用的应用场景 

Logo

一起探索未来云端世界的核心,云原生技术专区带您领略创新、高效和可扩展的云计算解决方案,引领您在数字化时代的成功之路。

更多推荐