zookeeper
zookeeper是什么?
zk是一个分布式的,容错的通用的协调服务.这个协调的含义是:在分布式系统中,有多个节点,当多个节点对一些信息认知不一致的时候需要一个服务来做协调,给出最终答案.zk就扮演了这个服务.
zk提供了很多功能,最常用的就是:metaData stage;Endpoint Discover;Lock;Leader Election
mit 分布式系统课内容
https://zhuanlan.zhihu.com/p/214144614
- zookeeper是一个通用的协调服务,相比于raft的一个库,zookeeper定义了一套通用的api接口,同时实现了这个接口并向外提供了服务.zk已经被集成到到了大量的服务中,同时已经取得成功.
2.zk是一个通用的,多副本容错的,多副本协调服务. 相对应的会有另外一个问题:如果有n个副本是否能够获取到n倍的性能.
3.raft协议对应的事zab协议.zk是运行在zab协议栈之上的服务. zk可以被看成是这样一个系统,外层是一个zk服务,内存包括一个zab协议,当客户端写入信息的时候,zk调用zab,将写入信息封装为Log存入到本地,然后将这个Log的拷贝发送给其他zk节点中的zab协议中去. 在zk中,我们添加更多的副本,这个时候并不能提升系统性能,而是添加了leader节点的负载.
4.在zk系统中,当我们添加更多副本的时候,并不能提升系统的写吞吐量,反而会增加leader副本的写入的压力.但是我们却可以增加读负载.其中zab的做法是:当一个写请求发送到leader节点的时候,leader节点将请求转变为一个Log,然后提交给zab,本地zab记录下当前的log,然后将对应的Log发送给集群中的其他服务的副本,当超过一半的副本节点接收成功的时候,然后本地才commit,然后给所有节点发送commit.然后给各个副本发送commit的命令.因为读的时候是从副本读的,这个时候可能不保证读取到最新的值,就是无法实现线性一致性.
一致性保证(8.5)
https://zhuanlan.zhihu.com/p/214146056
zk不保证线性一致性.他的一致性有些特殊/
写入一致性
zk有一种机制,让整个系统保证以某种顺序一次只处理一个写请求,且符合写请求的发生时间.如果一个写请求在另外一个写请求之前发生,zk也会先执行这个写请求,然后去执行另一个写请求.
读的局部一致性(Session一致性)
zk的任何一个客户端能够按照客户端指定的顺序来执行,雷士与FIFO客户端系列.就是如果这个客户端向zk集群发送了 w1,w2,w3的写入,然后发送读指令的话,这个客户端能够看到顺序是偏序的w1,w2,w3可能不相邻.同时一个客户端需要读取的数据是写入数据的一致性,同时也要保证后一个读不能倒推.即使客户端读取的数据换了一个副本也需要完成.
如何实现zk的读的局部一致性(Session一致性的)
zk的leader节点需要为所有写入的log添加上一个zxid,然后当写入的时候会给客户端返回对应的zxId,读取的时候也会返回对应数据的zxId.客户端从和某个副本建立的session读取到数据的时候,最记录下zxid,下一次读取的时候也会带上这个zxId.约定只读取比这个zxId打的日志.
todo 其中 zxid是如何返回的呢?
同步操作
如果zk的客户端在读的时候希望读取到当前集群的最小信息的时候,可以适用Sync指令,这个Sync可以理解为一个空的向Leader的写入.这种就表示一个语义:当Sync执行完成后,Sync这个时间点前的所有的消息都会返回.
readyFile是如何实现的
zk中是如何保证操作的原子性的.例如我们希望读取某个文件中的内容的时候,这个数据是否变更过的情况.
zookeeper中提供了一个watch的功能.在执行事务之前,会有一个对数据的假设,这个假设我们可以设计一个watch添加到zookeeper的fellow节点中.然后去执行我们的任务.当对应的数据发生了变更的时候,会先触发这个watcher通知到客户端,然后再去执行后续的操作.
同时watch是存放在Fellow节点的,可能会有一种情况是当某一个Fellow失效,需要切换到一个新的Fellow节点,这个是时候新的Fellow节点中是没有之前的watch的这个时候,客户端会受到切换Fellow节点的事件,然后将之前的watch重新watch到新的Fellow节点上去.
zk的API(9.1)
zookeeper提供的非功能性特性:
1.分区可用,当系统发生分区以后,依然能够正常适用.通过raft协议的框架来实现的
2.写线性顺序
3.读Session一致性,且能够有效的扩容读
4.能够提供watcher机制,实现原子操作
5.一个客户端发出的写请求都会被顺序执行
6.也提供了Sync指令帮助我们实现尽可能的一致性
zk的API
zk的API像一个文件系统,是一个层次化的结构./a/b/c/d
设计成这样的原因是:由于Zk需要被多个服务共享,需要区分多个不同的服务.所以需要一个命名系统,这个系统就被设计成了类似于文件系统.
zk的节点类型
zk的节点类型有:
1.持久节点:这种节点一旦创建就永远存在
2.临时节点:如果zk认为创建零时节点的客户端挂了,就会删除对应节点.znode会和客户端绑定在一起,客户端会定时的给服务端发送心跳,
3.时序节点:时序节点的名称是创建名字+序号,当有多个客户端同时创建时序节点的时候,zk会保证这些节点的序号是不同的且单调递增的.
zk的api语义
zk中所有api的操作对象都是znode节点.在api接口中表示znode的是path路径:
接口有:
-
创建 create(path,data,flag):
flag表示节点类型.create接口是排他的,同一个path的znode只有一个客户端能创建成功,如果存在了znode,那么就返回false,这个在锁当中会比较常见. -
删除delete(path,verseion):入参是文件的全路径和版本号
每一个znode都一个版本号,当我们试图删除或者修改znode的时候都适用这个版本号,这样能有效的避免并发中的更新丢失问题. -
exist(path,wathcer):
判断znode节点是否存在并添加一个watcher,其中判断znode是否存在和添加wathcer是原子操作,不存在两个操作之间添加一个新操作.
这里的watcher是表示对应的znode是否新增,删除,修改等事件. -
create(path,watcher):创建一个watcher
创建watcher主要是查看path的内容是否有改变 -
getdata(path,watcher):获取数据并添加watcher
获取path的数据,同时添加一个watcher,这个watcher主要是查看文件内容是否有变化 -
setData(path,data,version):
带有版本号的修改数据,只有版本号相同的时候才能修改成功,否则失败. -
list(path):
返回路径下的所有文件.zk的适用场景(9.2,9.3,9.4)
适用zk实现一个计数器
正常我们实现一个计数器,是从存储器中读取计数器的值,然后将计数器+1,然后讲结果写入到存储器中.
使用zk作为存储器来实现计数器的时候,可能会有一些问题.出现更新丢失的问题.zk的读是从Fellow节点读的,这个值不保证最新值.
修改完成后写入的时候是在Leader节点上.
当前客户端的在读取和写入的过程中可能有其他客户端的写入,当前客户端写入的话会覆盖之前其他客户端的写入.zk适用了mini-transaction的功能帮助解决这个问题:
为每一个znode设置一个version,没一次修改都会将version+1
在修改的时候参数都会有一个version,都会比较对应的version如果相同就运行修改,如果不同就删除.mini-trasaction代码示例:
while true : k,v := getData("path") if setData("path",k+1,v) break
以上代码能够解决更新丢失的问题,但是也会导致性能和死循环的问题:
1.如果竞争特变大,这个时候可能会导致需要n^2次进行计算
2.有一种情况是可能setData()永远都不可能为true的情况
3.我们可以在当前节点中添加一个随件时间的延迟来帮助避免并发的情况.
适用zk实现非扩展锁
zk可以实现分布式锁,锁的要求是如果获取到了锁就继续运行,如果没有获取到奥锁就等待,当锁被释放以后,阻塞的进程都会获取到对应通知,来争锁.
WHILE TRUE
IF CREATE (PATH,DATA,EPHEMERAL=TRUE) RETURN
IF EXIST(PATH,WATCH=TRUE)
WAIT
这种非扩展的分布式锁会触发羊群效应,其中时间的复杂度是 O(n^2).通知的次数是 [0,n-1]的之和.
的效应被称为羊群效应.非扩展锁锁会有羊群效应.
zk实现的可扩展的锁
可以适用顺序节点的方式创建锁,具体伪代码如下:
create("lock",data,sequential=TRUE,elephemeral=TRUE)
WHILE TRUE:
LIST "lock*"
IF NO LOWER #LOCK:RETURN;
IF EXIST(NEXT LOWER #LOCK , WATHC=TRUE):
WAIT
首先创建一个sequential的节点,这个节点会获取到一个序列号.
在一个循环中:获取到所有的序列节点,如果没有比当前序列号小的,就算获取到了锁.
否则就进入下一步 判断一下比当前序列小的znode是否存在且添加一个watcher.
如果成功,就等待watcher通知,如果exist失败了,这个时候返回重新获取列表并尝试获取锁因为exist失败的可能系有两种,一种是等待节点主动放弃了节点,第二种是之前节点失效了.所以重来一遍,有两种可能性:1.直接能获取到了锁,2.监控比上次获取到的小节点更小的节点.
zk的使用场景
命名服务
适用zk的create()顺序节点,这样每次都能够获取到一个更大的数值.
配置服务
适用create 和watcher,将系统中想要配置信息配置在zk中,同时发生变更的时候通知业务系统.
集群管理
1.选主
2.资源定位:当某个节点失效的且发生迁移的时候,这个时候client收到通知,重新链接到新节点中去.
分布式锁
zk的适用的场景
endpoint discover
leader election
Lock
Metadata storage
分布式锁中redis和zk的区别
todo
zk做服务发现的缺点
1.zk是cp优先的,例如三机房中,某一机房从网络中断开,这时候这个机房的注册中心将无法使用.这个时候可能会导致服务展示的无法调用.
2.zk的写是在线性一致性,无法通过添加机器实现扩容.
3.可以通过垂直切分服务,但是服务的联通是一个问题.(解决第二步的问题的方式)
4.很多rpc框架依赖zk做服务发现的时候,需要实现对zk的弱依赖例如(服务的上下线,缩扩容才依赖注册中心).这个时候需要注册中心的客户端做一些缓存,或者广播通知到依赖和被依赖的服务当前下线的情况.
5.zk的客户端异常的处理异常的复杂,常常会导致一些磁场的情况.
6.zk在大数据领域使用的非常的多,大数据中需要分割数据集.有多台机器并行的运行task.这种粗粒度的调度,选主,元数据存储等这种粗粒度的操作非常适合使用zk来实现.
zk的watcher的实现机制
1.client端和某一个Fellow节点创建Session以后.客户端使用 get和exist命令想某个znode节点添加watcher.前者是内容监听,后者是结构监听.
2.服务器端收到这个watcher以后,会在一个类似于map的接口中记录下来当前的待监听情况,其中执行命令和添加watcher这两个操作是原子的,中间不会插入额外其他的任务.
3.当leader节点的写入和修改的时间发生以后,在写入完成后,会从map中取出对应的watcher时间给客户端发送watcher,同时将这个watcher事件删除掉.
4.watcher事件只有一次有效,在收到通知以后,处理完成事件以后,需要再次注册才会有效.
5.其中客户端在收到通知然后处理这个watcher事件,然后继续添加watcher.在处理watcher和添加新的watcher中间有个间隔,这个间隔中间的如果发生变化是不会收到watcher的.
6.watcher的通知中只会包括事件信息,但是不包括其中的内容信息.
zk是如何保证数据一致性的
- zk中的一致性:zk的数据一致性读和写的一致性不同.其中写入的时候保证的事线性一致性.所有的时间都按照先后顺序一次写入到集群.读请求使用的事session一致性.各个客户端不能保证读取到的信息是最新的,但是能够保证每一个写入的数据能够按照顺序读取出来,且写入的数据一定能够读取出来.
- zk中的节点角色:leader,fellow,observer;其中写入操作只能在leader节点中,fellow能够读,observer只能读,不能投票
- zk写入的过程:当客户端通过session将写入请求发送到集群,这个请求会被转发给leader节点,leader节点获取到这个写入请求会生成一个log同时也会生成一个zxid(递增的),然后讲这个log写到一个fifo的队列中,然后封装一个proposal在集群中广播.当收到集群中一般的Fellow的ack以后,发起commit命令,然后ack本地.然后返回写入成功同时将zxid返回给客户端.
- 客户端读取数据的时候,会带上本地存放的zxid,来保证所有zxid之前的log都已经同步到了fellow节点来了,如果没有同步来,这个时候就等待,再返回.这样来保证session一致性.
zk的技术内幕
zk的系统模型
1.数据模型
zk是一个协调系统,需要服务多个业务,所以设计为了一个多路径的结构.每个路径是一个特殊的znode结构,既可以是文件,也可以有子节点.
2.节点的特性
znode的节点类型有三种,持久化,零时,顺序.前面两种和后面一种做一个交叉就有四种类型的节点.
临时节点不能有子节点,只能是叶子节点
zk中有一个全局的事务Id,zxid.同时czxid,mzxid,pzixid(子节点列表变更)
也会包括ctime,mtime等修改信息.
3.版本-保证数据的原子性
mini-transaction:在zk中对于znode的状态的修改的操作就是事务.和rdb中定义的对于数据库中状态的一组操作的集合被定义为事务会有一些差异.
zk的事务是通过version实现的.其中version用来帮助cas,防止更新丢失的.
4.watcher数据变更的通知
watcher用来帮助我们监控znode内容,子节点,节点的变化情况,并获取到通知.
整体流程是这样的:
1.在get,exist,getChild的时候带上一个watcher的时候,
2.首先会在客户端中记录下这个watcher,然后watcher封装在packet中,发送到zk的fellow节点中.
3.fellow节点中将当前的watcher记录在一个map中,当时间发生的时候才取出来.
4.当前fellow节点获取到一个写入事件的时候,发现触发了watcher.
5.从map中取出这个watcher信息,封装为一个watcherEvent(keeperState,EventState,path)信息.
6.客户端的SendThread获取到了WatcherEvent进行反序列化,发送到一个EventQueue中.
7.EventTreahd一次从EventQueue中获取出WatchEvent,校验是否存在watcher处理的对象,如果没有就丢掉,如果有就执行.为了保证事件的顺序性,这里只能单线程顺序处理.
note:
1.在get(),exist,getChild()在提交watcher的时候,查询操作和注册watcher是原子的,中间不会插入任何事件
2.客户端需要记录之前以及提交过的watcher,在只晓得会后会校验这不分watcher,同时当方式链接丢失的情况的时候,链接到其他Fellow副本的时候,需要重新注册watcher.
5.zk的权限
1.ugo(UserGroiupOther)权限:
2.acl(AccessControlList)权限:
用来保护多个业务共用集群的时候,保证当前业务的数据安全的功能.
acl包括三部分:
- 模式
1.digest :类似于user:password
2.ip:只有一条机器能够访问
3.world:特殊的digest,world:anyone - 权限
权限就是对数据的curd,包括:
1.create
2.delete
3.read
4.write
5.admin
- id
不同的schema能够指定不同的id
序列化与协议
序列化框架:zk中各个节点之间传递消息的序列化协议是:jute协议,这个是hadoop最开始适用的协议.后来hadoop开发了avro序列化协议,但是zk已经有大量适用了就没有替换新协议.
协议:zk自己定义了一套协议,|len|请求头|请求体| == |len|响应头|响应体|
客户端
zk的客户端的组成和链接通信过程
- zkClient包括的部分:
1.HostProvider
2.WatcherManager
3.ClientCnxn
4.SendThread
5.EventThread
6.ClientCnxnSocket
7.outgoingQueuue
8.pendingQueue :用来存放已经发送出去,等待结果的packet
9.evntQueue
- zkClient的处理过程
1.创建zkClient的实例zookeeper对象
2.初始化WatcherManager对象,并将可能的存在的默认的Watcher存放在本地
3.初始化HostProvider存放所有的host信息
4.开始创建一个ClientCnxn对象,负责处理客户端和服务器端的事件
5.初始化SendThread,和EventThread,这两个线程一个用来进行通信,一个用来处理当前的时间.
6.启动SendThread和EventThread,为处理远端信息而准备
7.从HostProvider获取一个IP地址,创建ClientCnxnSocket,建立长连接.
8.SendThread构造一个ConnectRequest,放到outgoingQueue中,等待ClientCnxnSocket发送到服务端去
9.ClientCnxnSocket从outgoingQueue中获取一个任务,封装为一个packet发送出去.
10.ClientCnxnSocket获取到了ConnectResponse,从中获取到服务器端分配的sessionId.
11.SendResponse获取到连接返回结果后,做一些初始化的工作,同时设置超时时间等.同时修改HostProvier表示当前的host连接成功
12.SendThread表示创建成功了,这个时候需要生成一个SyncConnected-Node的事件,发送个EventThread线程来处理.感知到连接成功
13.EventThread接受到事件以后,从watcherManager中查询出对应的Watcher,然后讲生成的WatcherEvent添加到eventQueue中等待EventTrehad处理
14.EventTrehad不但从 eventQueue队列中获取事件出来处理
zk的HostProvider地址
1.zk在连接的时候,随机选择一个ip地址连接远端的IP地址.如果有重复的,这个时候会重新选另外一个.
2.zk中也提供了一个chRoot地址空间.如果我们希望为这个zk地址添加一个地址空间,可以在host地址后面添加一个默认地址空间.
3.我们也可以自己实现对应的HostProvider空间,可以实现一个额外的功能,例如同机房优先,就是客户端在选择的时候优先选择当前机房的节点.
ClientCnxn:网络IO
-
Packet:
package是网络发送的包的封装,其中会包括很多的上下文信息.同时这个对象也帮助生成对应的二进制流的方法 -
outGoing和pendingGoing队列
outGoing和pendingGoing队列,前者用来存放带发送包,当发送完成以后等待结果的时候,会将这个包放在pendingGoing队列中 -
ClientCnxnSocket:
这个Socket是Zk自己的实现,也可以使用netty帮助实现.
这个socket用来负责发送包,首先从outgoing队列中获取一个packet并为这个packet生成一个xid,然后发送请求,同时将packet存放在pendinggoing中.
当信息返回的时候,会校验xid是否是需要的.
同时会根据当前的情况来处理:
1.当前客户端还没有生成,会被反序列化为一个connectResponse来处理
2.正常情况下,发现这个包是一个事件(报文中会有),会想eventQueue中添加任务.
3.如果是一个正常的请求,取出pendingQueue中的获取到对应的packet,然后反序列化出response提交给上层的业务处理. -
SendThread
是整个网络IO的核心部分,一方面定期的发送心跳包维护网络连接,当网络连接失效以后,重新链接.
另一方面,SendThread用接受上层的服务的请求,交给Socket发送.
最后:也用来处理WatcherEvent,将对应的事件交给EventThread来处理. -
EventThread
定期的从eventQueue中取出watch事件,并进行线性的处理.
会话
会话:客户端和服务器端建立连接后,就会生成一个SessionId.同时也就表示该客户端和服务端的Sesion的存在.
Session的状态
1.Connecting
2.Connected
3.ReConnecting
4.ReConnected
5.Close
会话创建
当客户端发送一个ConnectRequest的时候,一下几个步骤:
1.生成一个SessionId,具体算法是时间Id + myId帮助出AG你就按
2.生成一个Session对象,记录SessionId和超时时间,关闭状态,添加到SessionTracker中
3.当前的Session在各个服务器端流转
会话管理
- 会话管理
会话创建完成后,设置了超时时间,会定期的发送心跳给服务端,来保证这个心跳.
Session交给了SessionTracker管理:其中一种数据结构分桶策略来管理了所有过期的Session的列表.以过期时间为key,以sessionSet为value,然后定期的将过期的SessionSet清除掉. - 会话激活
触发激活的时机包括普通的读写请求和心跳请求.
当接受到激活的时候,会查出就的超时时间,计算出新的超时时间,然后从旧分桶中移除Session,向新分桶中添加Session - 会话超时检查
SessionTracker中有一个线程,计算出当前的分桶,然后将分桶中的SessionSet取出来,这些还存在的Session就是过期的Session.
会话清理
- 1.将Session设置为closing,这样就不接受请求了
- 2.广播给集群中的其他的Session,表示需要清除.
- 3.找出当前Session关联的临时节点.
- 4.将当前的临时节点都标记出来,生成删除的任务.
- 5.执行删除临时节点.
- 6.删除Session事务
- 7.关闭Connextion连接
会话重连
//todo 这里有一个问题是zk中的事务.
会话重连是客户端在执行的过程中,当建立的一个连接,丢失了,这个时候需要重新创建连接.这个过程就是会话重连.
这个时候也会出现一个问题:是SessionMoveException.的异常.
Leader在写入数据的时候,会校验Session的Fellow是之前建立的时候注册的,如果不是就包SessionMoveException.
服务器启动
//todo
leader选举
fastEletcion算法
- 进入leader选举阶段
- 当前节点新加入了集群
- 集群中的某个节点连接不上leader节点
- 当前集群中台:
1.集群中leader节点正常
集群中的节点将leader节点的信息发送个当前的节点.然后重新加入.
2.集群中leader节点不存在
这种leader节点不存在的需要进行选举流程. - leader选举的流程
1.当前节点状态为looking状态
2.初次投票,不知道其他机器的情况,都投给自己 (zxid,sid)并广播出去
3.更改投票,收到其他节点的投票后,根据(zxid,sid)选出最大的重新投出去
4.处理选票,将收到选票查过法定人数的选为leader节点,发广播.
实现细节
服务器的状态
LOOKING:选主阶段
FOLLOWING:跟随者阶段,便是当前当前是一个从节点
OBSERVING:观察者节点,表明当前节点是Observer
LEADING:领导者阶段,当前的服务器角色是Leader节点.
投票信息
1.id 当前服务器的myId,集群中所有的myId都不相同
2.zxid 当前服务器中最大的事务Id,全局递增
3.electionEpoch:当前选举的epoch,有一个logicClock来控制.
4.leaderEpoch: leader节点的epoch
5.state:当前服务器的状态
网络模型:
- 建立链接
选主阶段,是广播消息的,这个时候需要两两建立链接,一般会监听端口3888,位列避免重复建立链接,做了一个约定,只有myId大的和小的建立连接. - 消息队列:
- 接受消息队列
- 发送消息队列map:每一个myid都有一个就接受的队列
- 发送消息workerMap:每一个myid都有一个发送器
- 最后一条消息队列:最近发送过的消息,为每一个myId保留最后一条发送过的消息.
- 发送消息
其中,当发现发送消息队列为空的时候,会将最后发送的那一条重新发送一下,防止上次发送失败,同时接收方会去从 - 选leader的算法
1.当发现是looking状态的时候,将logicCloc++
2.够着veto信息,并准备投票
3.发送选票
4.判断当前服务器的状态为Looking状态,如果是就准备接收外部选票.这里是循环的开始 --> 循环结束是:某一个选票过半了,达成共识.
5.判断接收到的选票的epoch- 如果epoch大于当前选票,就清空之前收到的所有选票,修改logicClock,重新构造选票
- 如果epoch小于当前选票,放弃这张选票
- 乳沟epcoh等于当前选票,进行pk
6.选票pk
(zxId,myId)打的为最终选择的选票
7 查看是否需要变更选票,若干需要就变更选票,重新投票
8.统计选票,查看是否有过半的
9.如果没有,就继续统计,跳转到第四步,
10.更新服务状态.Leadring,Observing,
各服务器角色介绍
Leader
- 处理器责任链
当前请求到来的时候,会预先处理,然后放到一个队列中,这个队列中数据会进行投票同步到各个Fellow节点中,同时投票完成后,放入到ToBeCommitProcessor处理完成后交给FinalRequestProcessor处理首位工作,将数据提交到内存数据库中,同时返回结果. - LearnerHandler
当前Leader节点的其他的Fellower和Observer节点都抽象为Leaner.其中有一个LearnHandler负责和每一个Learner进行通信和交互.
Follower
- 处理非事务请求,转发事务请求
- 参与事务投票
- 参与leader选举投票
Observer
- 处理非事务请你去,转发事务请求到leader节点.
通信消息
消息的类型有:服务器建立,数据同步指令,事务提交,会话心跳
- 服务器建立
服务器启动的时候,需要和leader节点建立连接,交换信息ObserverInfo ,16 observer--> leader 表示当前节点 已经处理的 zxid
FellowInfo , 11 fellow --> leader 表示fellow服务器启动的时候,发送给leader的
LeaderInfo zxid leader --> learn 表示当前leader的zxid
ackepoch zxid learner --> leader 交换epoch信息给leader
newLeadewr zxid leader --> - 数据同步
当learner添加到集群中的时候,需要同步leader信息到本地.
全量同步,增量同步,回滚数据,同步完成,可以开始对外服务. - 事务提交
fellow转发事务到leader
leader 发起投票给 fellow
fellow ack投票给leader
leader commit fellower
leader 发送inform 信息给 observer
leader 发送 sync 新给 learnr,表示zxid的信息操作已经完成. - 会话信息
当建立了session的时候,需要有心跳
ping : 发起心跳
reveralidate 验证心跳是否游戏哦啊
请求处理
创建Session阶段
session的创建是一个特殊的事务操作,也需要各个Fellow节点进行广播和投票.
主要阶段如下:
1.请求接受
nioSocket中获取到了用户发送的消息,这个时候需要进行反序列化.确定当前的消息创建Session的消息.校验客户端的的zxid,协商过时时间等
2.会话创建
根据请求参数生成一个SessionId,同时创建session对象,添加到SessionTracker中.
3.预处理
创建一个创建Session的事务,提交给ProposalRequestProcessor对象.准备进行事务处理
4.事务处理
这里是同时调用三个事件,Sync(本地持久化),Proosal(发起投票),Commit(确认提交)
5.Sync流程
对当前事务请求日志记录下来
6.Proposal
适用zxid,在集群中广播当前的Proposal,然后等待commit,在服务端会暂时将这个Proposal放到toBeApplied队列中,然后等待ack的到来,Fellow中先调用Sync记录事务信息,然后回复ack.
leader收到足够的ack后,向Fellow发起commit,向Observer中发起Inform信息.
7.Commit流程
将当前事务从队列中取出来,交给FinalRequestProcessor处理.
8.事务应用
将当前的事务应用到内存中,便于后期查询,同时也提交到commitProposal中,这样便于Learner同步.
9.返回响应阶段
做一些统计的信息,同时创建Response信息,序列化返回.
setData请求处理
setData主要标准的4步:1.预处理,2.事务处理,3.事务应用,4.返回结果
1.预处理:主要是权限,会话,version等的检查,同时生成ChangeRecord记录在队列中.
2.事务处理:记录事务日志,发起投票,收集投票,提交投票.
3.处理事务:将事务应用到内存中,同时将记录放到commitProposal队列中.
4.返回结果:构造Response,反序列化同时将结果返回给客户端.
请求转发
由于所有事务性的操作都是由Leader节点来处理的,我们连接的事Learner节点.这个时候,请求抓饭会被转发给Leader节点处理.适用的协议是REQUEST来处理的.
getData的处理过程
getData是非事务操作,1.预处理:检查请求以及session 2.获取数据,检查acl,注册Watcher 3.创建Response,反序列化,返回结果.
数据存储
内存数据
1.DataTree
存放了所有的path信息.
2.DataNode:
最小的数据存储对象.
3.临时数据映射
是一个ConcurrentHashMap,其中key是SessionId,Value是DataNode对象.
4.ZKDB:
一个内存DB,存放了所有的DBTree,事务日志和会话,这些数据会定期的被dump到磁盘上去.
//todo 有些更加细节的就跳过了.
数据同步
leader节点中的数据有两种,一部分是在commitProposal队列中的,还有一种是在磁盘上的.相对应的,新加入的节点和commitProposal中的最大,最小的zxid的关系将会决定是否适用那种策略.
数据同步的方式
同步的过程中,会和数据提交的过程一样,会同事同步两条记录,一条是proposal一条是commit记录.
数据同步的策略
- diff同步
新加入的节点的zxid在commitProposal中,可以diff同步 - 回滚+diff同步
这个是之前有个leader,有一条记录已经记录了,但是还没有commit,导致.这个时候先回滚,然后在diff中的差异 - 回滚
和上面一样,但是没有多余的proposal - 全量同步
learner中的最大的zxid不在proposal中.直接全量同步