ZooKeeper保证数据一致性用的是ZAB协议。通过这个协议来进行ZooKeeper集群间的数据同步,保证数据的一致性。
两阶段提交+过半写机制:
ZooKeeper写数据的机制是客户端把写请求发送到leader节点上(如果发送的是follower节点,follower节点会把写请求转发到leader节点),leader节点会把数据通过proposal请求发送到所有节点(包括自己),所有到节点接受到数据以后都会写到自己到本地磁盘上面,写好了以后会发送一个ack请求给leader,leader只要接受到过半的节点发送ack响应回来,就会发送commit消息给各个节点,各个节点就会把消息放入到内存中(放内存是为了保证高性能),该消息就会用户可见了。
那么这个时候,如果ZooKeeper要想保证数据一致性,就需要考虑如下两个情况,情况一:leader执行commit了,还没来得及给follower发送commit的时候,leader宕机了,这个时候如何保证消息一致性?情况二:客户端把消息写到leader了,但是leader还没发送proposal消息给其他节点,这个时候leader宕机了,leader宕机后恢复的时候此消息又该如何处理?
ZAB的崩溃恢复机制
针对情况一,当leader宕机以后,ZooKeeper会选举出来新的leader,新的leader启动以后要到磁盘上面去检查是否存在没有commit的消息,如果存在,就继续检查看其他follower有没有对这条消息进行了commit,如果有过半节点对这条消息进行了ack,但是没有commit,那么新对leader要完成commit的操作。
ZAB恢复中删除数据机制
针对情况二,客户端把消息写到leader了,但是leader还没发送portal消息给其他节点,这个时候leader宕机了,这个时候对于用户来说,这条消息是写失败的。假设过了一段时间以后leader节点又恢复了,不过这个时候角色就变为了follower了,它在检查自己磁盘的时候会发现自己有一条消息没有进行commit,此时就会检测消息的编号,消息是有编号的,由高32位和低32位组成,高32位是用来体现是否发生过leader切换的,低32位就是展示消息的顺序的。这个时候当前的节点就会根据高32位知道目前leader已经切换过了,所以就把当前的消息删除,然后从新的leader同步数据,这样保证了数据一致性。
FAQ
Q:
- 同一个客户端,先发出写请求,leader将写请求事务广播给follow,如果半数follow成功,但是发出请求的follow没有成功,按照半数即成功的原理,leader会返回写操作成功,此时该客户端再读取数据,导致读取到的是旧的值,不符合同一个session写后读的保证
- leader对于一个事务在本地提交了,但是还没广播就down机了,那么从其余follow中选出的leader如何保证这个事务也被提交?
A:
1.zookeeper不保证读一致性,是弱一致性,如果要保证读到的数据是最新的,读取之前要使用sync方法
2.旧leader commit完就挂掉了,因为写入的follower肯定超过一半,新leader具有最大zxid,因此新leader就拥有commit的proposal,这时候只要提交该proposal并进行同步即可。如果旧leader只同步了一个follower就挂掉,该follower就是新leader,新leader同步该proposal然后同步即可。如果旧leader还没来得及同步就挂掉,该proposal在新集群中也不会存在,也不会成功,因此当旧leader恢复时就会被rollback。
Q:
客户端连接到某台follow,先执行写数据的操作,此时该操作会被发送到leader,然后原子广播事务提议,有半数follow同意了(假设发送这条写请求的follow由于某种原因没有没有同意),然后leader发送commit并让所有follow都commit(恰好发送写请求的follow又没有收到commit),然后leader返回写成功,之后同一个客户端再读取这个数据,因为这个客户端连接的follow并没有最终提交前面的写事务,就导致了写后读不一致,这个问题怎么解决的?
zab在崩溃选择时,根据什么决定proposal该被抛弃或者该被提交?比如最开始有5台server, leader把日志复制到了另外2台,leader在本地提交日志(或者还没来得及提交),然后立即挂了。剩下4台server, 有2台有这个日志,有2台没有,那么这个日志要不要保留?
A:
问题1:你说的是对的,但是 zookeeper 的读写一致不是在 server 做的,而是 server & client 配合的;client 会记录它见过的最大的 zxid (在你的场景下,就是这条写入 的 zkid),读取的时候,如果 server 发现 这条 zxid 比 server 端的最大 zxid 大,则拒绝,client 会自动重连到其他server(还在同一个 session) —— 最终会落到有新数据的 server 上,因为半数已经同意;
问题2:这个和选主有关系了,zookeeper 的选主,会尽量让 zxid 最大的那个 server 成为主;所以,在你的场景下,有最新数据的 leader 会成为主;然后再同步数据到其他机器,那么,这条 commit 的 log 便没有丢(相当于保留了);
读数据不是不保证一致性吗?有可能读到旧的
做sync后再读。当然,永远读到最新是不可能的。
sync后再读也不能保证读到新的. 因为sync之前如果刚好发生了选举,自己又连到老的leader上,老的leader还没有过期, 而sync目前又不要求quorum, 这种情况老leader就可能漏掉其他节点(新leader所在集群的)在sync之前发出的写请求. 不过ZK正在修复这点,会把sync改成要求quorum的.
对于问题2.只要数据被写进新leader的log了,均不会丢失,zk只要选出了leader,数据就是从leader这里为准了。leader的选举规则可以额外看看,总之就是zxid最大的当选。那么什么数据会被同步呢?只要被记录到log中的事务都不会丢失。那么可能你会疑问,如果事务没提交呢? leader在做restore的时候,会将日志中的proposal重新广播。
针对问题1;会有一个类似场景,就是比如clientA连接的服务端server1(follower),clientB连接的服务端server2(follower);clientA发起一个事务请求,只有server2没有应用这个事务。clientB去getData的时候,还是旧的数据。这种情况,哪些时机会去补偿?除了getData之前先执行下sync操作这个方法外,还有哪些时机吗?
没有了,zk不保证强一致性,所以clientb得到的数据可能为旧,这是zk cap设计中舍弃的,强一致性的数据可以从leader那里拿。
选举时发送的zxid是已经commit的还是log的? 我看源码FastLeaderElection里getLastLoggedZxid最终调的是zkDatabase的lastProcessedZxid,那么就算某个proposal被过半的follower log了,也有可能丢,因为选出的新leader不一定log了那个proposal
是log的,选举的时候是选取zxid最大的那个。选举成功需要过半节点在线,所以只要记录过半节点的记录就不会丢,因为新leader一定会含有这条记录
请教下大佬,对于第一个问题,Client写请求:leader对follow发送propose得到超过一半的follow的ack,那么对所有的follow执行commit。这里的是等所有follow都commit提交成功再返回给client写成功,还是发送完commit一半followcommit成功就给client返回写成功了?
ZooKeeper does not guarantee that at every instance in time, two different clients will have identical views of ZooKeeper data. Due to factors like network delays, one client may perform an update before another client gets notified of the change. Consider the scenario of two clients, A and B. If client A sets the value of a znode /a from 0 to 1, then tells client B to read /a, client B may read the old value of 0, depending on which server it is connected to. If it is important that Client A and Client B read the same value, Client B should should call the sync() method from the ZooKeeper API method before it performs its read.