zookeeper的ZAB协议学习
文章目录
- 1. zookeeper的复制状态机
- 2. zookeeper的角色
- 3. zab协议的阶段
- 4. zookeeper的数据模型
- 1. 在zookeeper每个服务节点的持久化数据
- 2. 内存中的状态
- 3. 选举过程中发送的信息
- 5.消息通信机制
- 6. zab的几个过程
- 1. leader选举过程 zabState为ELECTION
- 2. leader确认阶段
- 3. leader的数据同步阶段
- 4. 原子播报阶段,这个时候使用的是二阶段提交模式
- 7. 小结
- 1. 线性一致性读
- 2. 与client的线性一致性保持
- 部分源码,附
- 脑图,帮助理解
- 部分参考链接
看完raft的论文,再来看zookeeper关于ZAB协议的介绍,感觉raft写的真是好,zookeeper我能够搜索到的论文实际上都只是讲了一个大概,感觉 这一篇还算是稍微讲了一些东西,但是还是很不全面。迫不得已看了一下源码,只看了leader选举部分的实现 FastLeaderElection,以及leader选举相关的工作,其实zookeeper的项目对于java开发人员还是很友好的,直接 git clone
下来,在本地看的很爽。
下面尝试按照raft论文的方式来对zab协议进行阐述
1. zookeeper的复制状态机
zk的数据存在内存当中(高性能),但是同时记录操作日志+内存快照(二进制),持久化(类似于Redis)。
状态机+命令日志:内存中保存数据的最终状态,命令日志中保存所有的操作过程,内存快照中保存某一时间节点的状态机中的数据。
所以zk和raft基本一致,也是复制状态机的工作模式,由日志复制的线性化来保证系统的线性化。
2. zookeeper的角色
- LOOKING:进入leader选举状态
- FOLLOWING:leader选举结束,进入follower状态
- LEADING:leader选举结束,进入leader状态
- OBSERVING:处于观察者状态
写的话写leader,读取的话可以通过FOLLOWING,OBSERVING,而且拓展OBSERVING的话可以提供更多读的能力,但是不会降低写入的速度。
3. zab协议的阶段
这个阶段的划分不同的论文好像有不同的说法,这里先以zookeeper的代码中的为准介绍一下,再引述一些其他的方式,在zookeeper的源码中对zabState是这样定义的,有4种状态
ELECTION : leader选举阶段
DISCOVERY: leader确认阶段
SYNCHRONIZATION: 数据同步阶段
BROADCAST: 原子播报阶段
也有一些文章前面三个阶段合起来称为崩溃恢复阶段,这种也是可以的,这种情况下zab协议就被描述为奔溃恢复和原子播报两个阶段。
4. zookeeper的数据模型
1. 在zookeeper每个服务节点的持久化数据
这一块儿介绍的可能不是很全,主要关注了和选举相关的一些数据
字段 | 含义 |
---|---|
logs[]: | 日志 |
zxid : | 最后的log的zxid,这个zxid是一个64位的数字,高32位被称为epoch,类似raft日志中的term, 低32位是递增的counter类似于raft中的index,但是这里的counter不是全局递增的,每次leader选举出来之后,counter会被初始化为0,但是zxid还是全局递增的。所以日志是全局有效的。 |
epoch: | zxid的高32位,会单独持久化 |
lastCommited | 最新的commited的zxid |
2. 内存中的状态
字段 | 含义 |
---|---|
logicalclock : | 这个是选举专用的逻辑时钟,在服务启动后第一次选举开始的时候会初始化一个FastLeaderElection实例,logicalclock是他的一个属性,会被初始化为0;后期有可能因为一些异常原因重建这个实例,默认情况下服务不重启,这个logicalclock会是递增的状态,而且在zookeeper的代码中,有些地方把这个也叫epoch或electEpoch,颇具迷惑性 |
proposedLeader: | 当前节点认为的应该做leader的server id,根据当前节点收到的广播消息会动态变化,选举刚开始的时候初始化为当前节点的sid |
proposedZxid: | 对应的应该做leader的server的zxid,根据当前节点收到的广播消息会动态变化,选举刚开始的时候初始化为当前节点的zxid |
proposedEpoch: | 对应的应该做leader的server的epoch,这个epoch是zxid中的epoch,但是不一定相等,因为新的epoch生成了,但是包含这个epoch的zxid可能还没有生成,,根据当前节点收到的广播消息会动态变化,选举刚开始的时候初始化为当前节点的epoch |
state | 每个节点处于的角色状态,可能是LOOKING,FOLLOWING,LEADING,OBSERVING,会随着选举过程逐渐变化,在节点启动或者当前节点要发起leader选举的时候是LOOKING,leader选出来后是后面三种的一种 |
zabState | 每个节点处于的zab协议的阶段,可能是ELECTION,DISCOVERY,SYNCHRONIZATION,BROADCAST,在节点启动或者leader选举开始的时候初始化为ELECTION,选举完成后epoch的确认阶段为DISCOVERY,数据同步阶段为SYNCHRONIZATION,数据同步完成之后是原子播报阶段,对应的则是BROADCAST |
Map<Long, Vote> recvset: | 用来收集looking状态下的大家的选票信息,key是投票者的server id, Vote是对应的server投出的票,这个map数据结构是当前server用来记录同样处于LOOKING状态的server发出来的投票信息,如果这个达到了多数一致,那么久认为leader选出来了。 |
Map<Long, Vote> outofelection : | 这个对应收集的是leader或者是follower或者leader发出来的信息,这个也是按照多数生效(也就是超过半数的leader+follower信息发过来才认为真正找到了leader,感觉这个还是比较严格的),同时还会要求必须有leader广播的信息认为自己是leader. |
上面字段中的proposedLeader,proposedZxid,proposedEpoch,logicalclock是创建本地广播的选票信息的主要来源(new Vote对象的时候使用到这些变量),所以我们为了下面描述起来更加方便,将这些变量称为本地选票信息
。
vote的信息
字段 | 含义 |
---|---|
leader | 投票认为的leader的server id |
zxid | 认为的leader的zxid |
electionEpoch | 选举的逻辑时钟logicalclock |
state | 投票者的server state ,一般是LOOKING |
configData | 集群的服务器配置,用来验证quorum,这个字段应该是包含了当前集群有哪些节点 |
peerEpoch | 被认为是leader的节点的epoch |
vote的信息是一个选票的信息,就是下面广播的投票信息是一致的
3. 选举过程中发送的信息
字段 | 含义 |
---|---|
leader | 投票认为的leader的server id |
zxid | 认为的leader的zxid |
electionEpoch | 选举的逻辑时钟logicalclock |
state | 投票者的server state ,一般是LOOKING |
configData | 集群的服务器配置,用来验证quorum,这个字段应该是包含了当前集群有哪些节点 |
peerEpoch | 被认为是leader的节点的epoch |
5.消息通信机制
在正式了解zookeeper的zab工作模式以前有必要先简单介绍一下zookeeper的通信方式,更加有助于理解。
- 在zookeeper中,服务器中的连接是两两互联,构成网状状态,server与server之间直接使用的socket的长连接(俗称BIO),每两个server之间只会建立一个连接,sid大的去主动连接sid小的。
- 消息的发送不像http模式下一个请求过来之后要返回一个相应那样一一对应。因为tcp是全双工的,流式的,所以这里请求和相应是独立的,也就是可能连续发了5条消息,后面又收到其中3个消息的答复,这种哪条消息是请求,哪条消息是答复,需要通过消息的类型进行识别,消息之间的配对(a是b的答复)也需要通过消息号等匹配起来。
- 同样的,zookeeper是使用tcp长连接来保证接收方接收到的消息的顺序是和发送方发送消息的顺序是一致的,这个也是实现数据全局有序的重要保证
6. zab的几个过程
1. leader选举过程 zabState为ELECTION
下面的代码部分都在FastLeaderElection,方法lookForLeader()
作为入口
- 选举开始之后,每个server都会初始化内存中的状态部分中的proposedLeader(使用当前节点的sid),proposedZxid(使用当前节点的zxid),proposedEpoch(使用当前节点的epoch),state(为LOOKING),zabState(为ELECTION) 作为当前节点的本地选票信息,然后广播出去一个投票信息,广播的信息的格式就是上面的
选举过程中发送的
信息,leader字段使用的是proposedLeader,zxid是proposedZxid,peerEpoch 是proposedEpoch (注意着三个信息因为最开始初始化的时候是本机的信息,所以这里广播出去的也是本机的信息,但是随着选举过程的推进,后面可能就不是本机的信息了,但是后面介绍的其他几个字段都还是本机的信息) electionEpoch是当前机器的logicalclock,state是当前节点的state,configData是当前节点的config,等不再赘述,可以直接参考上面的表格。 - 然后如果当前是looking状态的话就会等待和收取广播消息(可能是自己发出去的,也可能是别人发出去的)假设收取的广播消息为n,
如果是自己的,就直接记录到
recvset
当中,key是当前的sid,如果是别人的,而且n.state是LOOKING,先比较选举逻辑时钟electionEpoch
- 如果广播消息中的逻辑时钟和当前节点的logicalclock一样大小,则比较n的选票信息和当前节点的本地选票信息比较(主要就是当前节点的proposedLeader,proposedZxid,proposedEpoch),比较的规则参看下面的
选票信息比较规则
- 如果选票n胜出,则修改本地的选票信息,主要就是当前节点的proposedLeader,proposedZxid,proposedEpoch
- 如果n没有本地选票信息新,则不作更新
- 将选票n放入recvset当中,key为n.sid,对应消息的发送方sid
- 广播本地选票信息(可能会重复发送,不影响)
- 如果本地逻辑时钟落后,则直接把本地之前收到的选票全部作废(清空
recvset
),重置当前节点的logicalclock为消息中的electionEpoch,然后进行选票信息比对,注意这里收到的选票信息不是和本地选票信息比较(因为proposedLeader,proposedZxid,proposedEpoch在对应的之前的electionEpoch中可能已经被改变过了),而是和本机的sid,zxid,epoch信息比较- 如果选票n胜出,则修改本地的选票信息为n中对响应的信息,主要就是当前节点的proposedLeader,proposedZxid,proposedEpoch
- 如果n没有本地选票信息新,则更新本地选票信息为本机信息(和选举刚开始时候的初始化信息是一致的)
- 将选票n放入recvset当中,key为n.sid,对应消息的发送方sid
- 广播本地选票信息(可能会重复发送,不影响)
- 如果n中的逻辑时钟electionEpoch小于当前节点的logicalclock,则忽略这个选票信息
- 查看recvset中的信息是否对本地选票中的proposedLeader达成了多数一致
- 如果未达成则进入大步骤中的2,继续处理收到的广播消息
- 如果达成多数赞成proposedLeader,则会继续轮询接收消息的队列看看有没有优先级更高的选票(这里的优先级更高的就是使用下面的选票信息比较规则最后胜出)
- 如果在处理队列中的消息发现有优先级更高的消息,则会把这个消息再放回到接收消息的队列中,跳到大步骤中的2,继续处理收到的广播消息,选出更加合适的leader
- 如果接收消息的队列已经空了,且没有优先级更高的选票,则会等待200ms
- 有优先级更高的消息的话,同3.2.1,跳到下一轮大循环
- 有消息,优先级不高,同3.2.2,继续消费队列
- 还是没有消息,可以认为这次选举结束了,将当前server的state设置为leading,following,observing三个状态中的一个,同时清空接收消息的队列
- 如果广播消息中的逻辑时钟和当前节点的logicalclock一样大小,则比较n的选票信息和当前节点的本地选票信息比较(主要就是当前节点的proposedLeader,proposedZxid,proposedEpoch),比较的规则参看下面的
如果是别人的,而且n.state是OBSERVING,接着进入步骤2,不以OBSERVING的消息为准,因为他没有选举权限
如果是别人的,而且n.state是FOLLOWING,LEADING,这个时候
- 判断n和本地是否处于用一个选举周期中n.electionEpoch == logicalclock.get(),如果在一个选举周期中则放入recvset,并查看recvset中的信息是否对n选票中的n.leader达成了多数一致,如果达成了并且leader的选票也认为自己是leader,那么就结束选举,这种情况应该是本次选举基本可以认为结束了,但是当前节点收到的消息比较滞后的情况,这个时候可能会受到leader和其他follower的信息,这个时候还是按照原来的规则计算即可
- 如果上一步没有计算出来leader放入另一个计票容器outofelection当中,这一步没有要求选举周期是一致的,然后看看outofelection中是否对n.leader达到了多数一致性,如果达成了也会结束选举,这个应该应用的场景就是类似某个follower和leader失去联系了,然后发起选举,结果收到了其他人都告诉他leader存在的消息(这个时候消息的选举周期和当前节点肯定不一致),然后,当前节点就会接受当前leader存在的事实,防止频繁进行选举过程。
选票信息比较规则
1.谁的peerEpoch高谁谁胜利
2.如果peerEpoch相等,则谁的zxid更大谁胜出
3.如果peerEpoch,zxid都相等,那么谁的sid大谁胜出
4.这里补充一个疑惑点,为什么还要先比较peerEpoch,直接比较zxid不就行了么,因为zxid不是包含了epoch的信息么,肯能是因为某个节点当选了master然后很快超时了重新选举了?有待后续探索
2. leader确认阶段
zabState为DISCOVERY,对应代码在QuorumPeer的run()
方法之中,这里针对不用角色的节点leading,following,observing,都会有DISCOVERY阶段。
这个阶段就是leader会生成新的epoch(从各个follower收集到的最大的epoch+1),并使用(epoch,0)
组合生成zxid,把自己的zxid封装成Leader.LEADERINFO包发送给发送给follower,然后follower确认这个epoch是大于等于自己当前看到的epoch的。如果不是就会抛异常,不承认当前leader(理论上不应该发生),如果接受了就会更新当前服务器的epoch,封装成Leader.LEADERINFO包发送给leader,在leader收到过半的follower的ack消息之后就说明大家都承认他是leader了,后面就可以开始数据同步工作了。
这里的epoch一般是和zxid中的一致的,因为follower的都是从master的消息当中得到的。
3. leader的数据同步阶段
在过半follower回复了ack消息之后,leader就可以开始数据同步工作了,数据同步的时候是采用强leader的方式,也就是大家的数据都要和leader的对齐,这时zabState为SYNCHRONIZATION
- 如果follower的zxid比leader的小则leader会发送后面的数据给对应的follower(也是使用两阶段提交的方式),在发送完后会发送一个Leader.NEWLEADER数据包,follower在同步完成后发送响应的ack消息
- 如果follower的数据zxid比leader的大,则对应的数据都会被删除,完成后也要发送ack消息
在leader收到过半的follower的ack消息之后就认为数据同步完成了,后面就可以进入原子播报阶段了
4. 原子播报阶段,这个时候使用的是二阶段提交模式
zabState为 BROADCAST
- 对于每一个事务请求过来的时候,都要由leader进行FIFO处理,假如是follower或者observer接收到了这个请求,那么会把这个请求转发给leader
- leader接收到事务请求后,会先生成一个zxid(epoch+有序递增的事务id),然后将该事务存储到本地日志,
- 接着将这个事务广播所有的follower进行事务日志存储,
- follower 在接收到leader事务请求时,要么选择执行该事务,要么选择抛弃leader,重新发起leader选举(但是会变成无效,直到他重新认领原来的leader,又会通过同步的方式进行数据同步)
- 等待过半的follower都回应ack表示可以存储之后,再发送一个commit信息给所有的follower进行提交(更新lastcommited并应用log到状态机当中),然后本地也进行一个提交(更新lastcommited并应用log到状态机当中),之后返回给客户端成功。
zk的leader处理事务时FIFO机制保持了数据的一致性,这个可以保证leader上的顺序性,同时,leader在给follower的信息传递中也通过tcp的有序机制保证了follower每台节点上的日志的顺序一致性。所以日志可以保持全局有序性,这个和raft是一致的。
7. 小结
1. 线性一致性读
zk的写是具备线性一致性的,但是读的话要分两种情况,如果是普通的read,则不满足线性一致性,因为读取没有走zab的流程,这个时候这个请求可能到了某个follower,而该follower还没有同步到这个数据的话,可能读不到最新的数据,但是这只是zookeeper对读的一种优化,可以更快响应,如果对数据的及时性有非常高的要求的话,那么园长(zookeeper也被称为动物园园长)也提供了线性一致性的读方式,就是在真正读取之前调用一下zk.sync()
方法,这个方法会获取当前最大的zxid,知道本机提交这个zxid才会返回,也就保证了在这之前执行的操作在本机都是可见的了。
2. 与client的线性一致性保持
从leader的选票比较规则以及多数投票一致性上可以得出,
- 肯定不会丢commit的数据,应为这个log必然已经存在了多个节点上
- 同时,对于只存在之前leader上的数据,如果leader挂了那么他的只有自己有的数据也会被忽略掉,即使他后来又回来,但是这个时候epoch偏低,所以不会接受他的日志。
- 但是对于那种有部分机器接受到了广播请求,只是存了log,这个时候leader挂了,该数据还没有走到commit阶段,这个时候如果新的leader恰好有这条数据,那么从zookeeper来看他是会保留这一条数据,在raft协议中,这样的数据也是会被保留,但是raft要求client重试请求的时候也要携带请求编号,来确认这个请求是否已经做了,而不会重复做两次(比如是创建节点),zookeeper这块是如何做的呢,解析zookeeper的log中发现石油session和cxid,zxid的,这样的话也就满足了幂等,可以通过这个来判断这个操作是否已经提交了。
事务日志
ZooKeeper Transactional Log File with dbid 0 txnlog format version 2
1/20/20 4:29:59 AM UTC session 0x30014da58050000 cxid 0x0 zxid 0x10000000c createSession 300001/20/20 4:30:00 AM UTC session 0x30014da58050000 cxid 0x4 zxid 0x10000000d create '/test_zookeeper/test/item,,v{s{31,s{'djdigest,'CgcA1GMivoBYyZZWuQDgeLuz5L45jmuVDyLKi2J0swQ=:MEonRpvlUHyT9yHsCnPddPJ0QVCMGVM5ylIV0Zv/VaY=}}},F,21/20/20 4:30:00 AM UTC session 0x30014da58050000 cxid 0x5 zxid 0x10000000e delete '/test_zookeeper/test/item1/20/20 4:30:00 AM UTC session 0x30014da58050000 cxid 0x6 zxid 0x10000000f setACL '/,v{s{31,s{'djdigest,'T9ihPbFmp0odTgrtigbbYJgBkC5Pe6XkWO543Hl1+jc=:dk8WmGqk2QsbWdyxv98BRJWaiW3xEGxpvbzVVx8z8ig=}}},21/20/20 4:30:00 AM UTC session 0x30014da58050000 cxid 0x8 zxid 0x100000010 setACL '/zookeeper,v{s{31,s{'djdigest,'V4AoltP7EqnmD6tlXT9D+yzozXnf2aN/FTmYOVekewQ=:vvEn1n8041x6LDHUgnGC/+tAAwKpFePtXZAdWP4hY3Y=}}},2
部分源码,附
/*** Messages that a peer wants to send to other peers.* These messages can be both Notifications and Acks* of reception of notification.*/public static class ToSend {/** Proposed leader in the case of notification*/ long leader;/** id contains the tag for acks, and zxid for notifications*/ long zxid;/** Epoch*/ long electionEpoch;/** Current state;*/ QuorumPeer.ServerState state;/** Address of recipient,这里是接收端的server id,实际上这个字段的信息并不会发出去*/ long sid;/** Used to send a QuorumVerifier (configuration info)*/ byte[] configData = dummyData;/** Leader epoch*/ long peerEpoch;}
public class Vote {private final int version;private final long id; //leader的idprivate final long zxid; // leader的zxidprivate final long electionEpoch; // leader的逻辑时钟logicalclockprivate final long peerEpoch; //leader的epoch}
zxid的初始化
这个方法会找到当前收到的最大epoch然后执行+1操作得到当前的epoch
long epoch = getEpochToPropose(self.getId(), self.getAcceptedEpoch());
zk.setZxid(ZxidUtils.makeZxid(epoch, 0));
epoch和clock是不是一个,东西,每个选票的信息有
每一个投票者会维护一个
Map<Long, Vote> recvset = new HashMap<Long, Vote>();
用来记录自己收到的投票信息
map的key是server id,也就可以收集当前选举轮次中每个server的投票信息
vote的数据结构是期望leader的id,期望leader的zxid,期望leader的投票周期(逻辑时钟),期望leader的zxid中的epoch部分
recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));
找到和当前认为的leader是一致的vote
voteSet = getVoteTracker(recvset, new Vote(proposedLeader, proposedZxid, logicalclock.get(), proposedEpoch));
脑图,帮助理解
部分参考链接
https://www.jianshu.com/p/90e00da6d780
https://zhouj000.github.io/2019/02/11/zookeeper-03/
https://www.cnblogs.com/leesf456/p/6140503.html
zookeeper的ZAB协议学习相关推荐
- Zookeeper之ZAB协议
什么是Zab协议 Zab 协议的作用 Zab 协议原理 Zab 协议核心 Zab 协议内容 原子广播 崩溃恢复 如何保证数据一致性 Zab 协议如何数据同步 如何处理需要丢弃的 Proposal Za ...
- Zookeeper 的 ZAB 协议
Zookeeper 的 ZAB 协议 目录 ZAB 协议 ZAB 协议介绍 ZAB 协议内部原理 ZAB 与 Paxos 算法的联系与区别 1. ZAB 协议 ZAB 协议并不像 Paxos 算法那样 ...
- Zookeeper之ZAB协议详解
ZAB协议 1.ZAB协议是专门为zookeeper实现分布式协调功能而设计.zookeeper主要是根据ZAB协议是实现分布式系统数据一致性. 2.zookeeper根据ZAB协议建立了主备模型完成 ...
- zookeeper的zab协议原理
zookeeper的集群特点 顺序一致性 客户端的更新顺序与它们被发送的顺序相一致. 原子性 更新操作要么成功要么失败,没有第三种结果. 单一视图 无论客户端连接到哪一个服务器,客户端将看 ...
- 分布式一致性算法Paxos,Zookeeper的ZAB协议,Raft算法
Paxos算法: Paxos一直是分布式协议的标准,但是Paxos难于理解,更难以实现,Google的分布式锁系统Chubby作为Paxos实现曾经遭遇到很多坑. 阶段一: 1.Proposer选择一 ...
- Zookeeper理解---ZAB协议
ZAB协议 Zookeeper并不是完全采用Paxos算法,而是使用了一种称为Zookeeper Atomic Broadcast(ZAB,Zookeeper原子消息广播协议)作为数据一致性的核心算法 ...
- 阿里一面,说说你对zookeeper中ZAB协议的理解?
又到了金三银四的时候,大家都按耐不住内心的躁动,我在这里给大家分享下之前面试中遇到的一个知识点(ZAB协议),希望对大家有些帮助.如有不足,欢迎大佬们指点指点. ZAB协议虽然舍弃分布式协议中的可用性 ...
- zookeeper的zab协议工作原理
原子广播 说zab协议之前必须提一下 paxos 协议 paxos协议主要就是如何保证在分布式环网络环境下,各个服务器如何达成一致最终保证数据的一致性问题 ZAB协议,基于paxos协议的一个改进. ...
- zookeeper的zab协议工作原理之原子广播
说zab协议之前必须提一下 paxos 协议 paxos协议主要就是如何保证在分布式环网络环境下,各个服务器如何达成一致最终保证数据的一致性问题 ZAB协议,基于paxos协议的一个改进. zab协议 ...
最新文章
- EventBus的使用(一看就懂)
- jQuery控制表格垂直滚动条
- 【.NET深呼吸】基础:自定义类型转换
- 设计模式6---(单例模式的概念及其实现(懒汉式和饿汉式),线程安全)
- python怎么开发软件_怎么使用python进行软件开发
- 递归走迷宫java,java递归实现的迷宫游戏
- TODA-MES电池行业解决方案
- ORACLE SQL分组查询某列或某几列重复信息数量
- 异步操作之后让await后续的代码能够继续执行
- php 通过ip查询地区,php怎样根据ip地址查地区
- xmapp_mysql端口冲突解决
- 语音助手——未来趋势
- 微信公众平台配置——URL、Token和JS安全域名
- 关键明 抖音 打造百万爆款变现文案
- 未授权和敏感文件泄露
- 记录---ClassCastException: java.util.Date cannot be cast to java.sql.Date报错解决方法
- 前端七十二变之html和css入门
- c#+CAD动态移动效果
- [COCI2014-2015#7] TETA
- 拼多多 2020校招 多多的排列函数(找规律 构造)