深入理解Zookeeper数据一致性问题

Zookeeper能保证任何时刻读到的数据绝对一致吗?
Zookeeper的特点就是,分布式,高可用,自带容错,所有节点读到的数据都是一致的。使用的场景通常是微服务的注册中心,或者一些分布式的开源软件用来保存元数据,或者监测生命状态。
这些使用场景默认Zookeeper永远是可用的,而且去Zookeeper集群旗下的每家分号,获取的数据都是一样的,通常情况下也确实如此。
也就是说可用性和一致性是Zookeeper的关键特性,作为一个消息的中间商,做了一个可靠的信息传递和存储的角色。
但是了解下Zookeeper的ZAB协议,特别是写入部分和读部分,发现了一点细节,Zookeeper不能保证永远读到最新的数据,这里简述下Zookeeper读写过程、
写过程
Leader接收Client的写请求,广播给其他Follower节点,其他节点将消息加入待写队列,向Leader发送成功消息,过半的Follower同意后,Leader向所有节点发送提交消息,Follower会落实写请求
读过程
Client直接访问一台 Zookeeper获取信息
也就是说,如果在写的过程中,过半的follower同意了,这条消息通过写入,但有一台Zookeeper和Leader无法通信了,或者因为磁盘,内存等原因拒绝写入了。此时一个client来这个zookeeper节点取数据,那么取的和最新版本的就不一致。
Zookeeper保证了怎样的一致性
有些地方会写Zookeeper不保证强一致性,保证了最终一致性。
只是从字面来看,最终一致性听上去也没错,但是从细节来看,还是不准确或者说不对。
首先,CAP中,Zookeeper保证的是CP还是AP。
随便搜一个科普CAP理论和Zookeeper关系的文章
Zookeeper和CAP理论及一致性原则
可以知道,Zookeeper保证的是CP,即一致性(Consistency)和分区容错性(Partition-Tolerance)。
而牺牲了部分可用性(Available),
在强一致性的条件下,必须先暂停服务,达成一致性再提供服务,这个同步过程中,请求得不到回应,降低了可用性。
而Zookeeper作为协调服务,需要在大部分情况下都可用,不能出现太明显的不可用,因此明显不可用的时段只有Leader选举阶段,此时无法写入,
Zookeeper选举机制本身是一种快速选举的机制,触发选举的时候有崩溃恢复和启动选举 两种情况,所以这个问题也可以控制。
从上文简述的写入机制来看,Zookeeper是通过Leader来让各节点的写入达到一致性。
而达成的一致性,但是这个过程中为了快速响应客户端,只要follower过半回应即可。
下面说一下几种一致性的概念
强一致性:又称线性一致性(linearizability ),

  • 1.任意时刻,所有节点中的数据是一样的,
  • 2.一个集群需要对外部提供强一致性,所以只要集群内部某一台服务器的数据发生了改变,那么就需要等待集群内其他服务器的数据同步完成后,才能正常的对外提供服务
  • 3.保证了强一致性,务必会损耗可用性

弱一致性:

  • 1.系统中的某个数据被更新后,后续对该数据的读取操作可能得到更新后的值,也可能是更改前的值。
  • 2.即使过了不一致时间窗口,后续的读取也不一定能保证一致。

最终一致性:

  • 1.弱一致性的特殊形式,不保证在任意时刻任意节点上的同一份数据都是相同的,但是随着时间的迁移,不同节点上的同一份数据总是在向趋同的方向变化
  • 2.存储系统保证在没有新的更新的条件下,最终所有的访问都是最后更新的值

顺序一致性:

  • 1.任何一次读都能读到某个数据的最近一次写的数据。
  • 2.系统的所有进程的顺序一致,而且是合理的。即不需要和全局时钟下的顺序一致,错的话一起错,对的话一起对(目前网上能查到的原话)

前三种应该都好理解。强一致性就是在任意时刻,所有节点中的数据都是一样的。
弱一致性就是可能访问的到更新后的值,也可能访问不到。
最终一致性,不保证任何节点都是相同的,也就是说各节点的数据版本可能完全是混乱的,a节点是1,b节点是2,c节点是3,然后a节点更新到2,b节点更新到3,但能保证在没有更新后达成一致。
顺序一致性第一句比较好理解,第二句就比较抽象了,字看的懂,但还是不知道具体说啥,经过查阅资料

Write(x, 4):写入x=4
Read(x, 0):读出x=0
1)图a是满足顺序一致性,但是不满足强一致性的。原因在于,从全局时钟的观点来看,P2进程对变量X的读操作在P1进程对变量X的写操作之后,然而读出来的却是旧的数据。但是这个图却是满足顺序一致性的,因为两个进程P1,P2的一致性并没有冲突。从这两个进程的角度来看,顺序应该是这样的:Write(y,2) , Read(x,0) , Write(x,4), Read(y,2),每个进程内部的读写顺序都是合理的,但是这个顺序与全局时钟下看到的顺序并不一样。
2)图b满足强一致性,因为每个读操作都读到了该变量的最新写的结果,同时两个进程看到的操作顺序与全局时钟的顺序一样,都是Write(y,2) , Read(x,4) , Write(x,4), Read(y,2)。
3)图c不满足顺序一致性,当然也就不满足强一致性了。因为从进程P1的角度看,它对变量Y的读操作返回了结果0。那么就是说,P1进程的对变量Y的读操作在P2进程对变量Y的写操作之前,这意味着它认为的顺序是这样的:write(x,4) , Read(y,0) , Write(y,2), Read(x,0),显然这个顺序又是不能被满足的,因为最后一个对变量x的读操作读出来也是旧的数据。因此这个顺序是有冲突的,不满足顺序一致性。
顺序一致性内存模型是一个被计算机科学家理想化了的理论参考模型,它为程序员提供了极强的内存可见性保证。顺序一致性内存模型有两大特性:
一个线程中的所有操作必须按照程序的顺序来执行。
(不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见。
这里的顺序一致性,讲的是一种多线程并发执行下理想情况,包含两种要求
1.线程中的操作必须按照程序的顺序执行,也就是说,不能自己自作主张,更换执行顺序
2.线程中的操作是原子性的,执行了就是执行了,没执行就是没执行,不存在中间状态,而且一旦执行,其他变量应该立刻可见。
联系到zookeeper,说点结论
1.各节点的数据更新必须按照顺序进行。
2.数据写入执行情况,数据版本应对其他节点可见(leader能知道写入是否成功)。
结合以上,你会发现,zookeeper并不是最终一致性,而是顺序一致性。
1.最终一致性的特点是,无法保证任意节点在同一时间某份数据是相同的,但是最终在没有新的更新时会达成一致。
而Zookeeper所有节点的数据版本都是递增的,可能会有某个节点因故障版本低于大多数,但是是有序的,不会出现各自增长的情况。
比如,Zookeeper节点可能会出现4台数据是version 5,一台数据是version4。但是不会是5台机器各自更新。
所以这里对顺序一致性的定义是
1.任何一次读都能读到某个数据的最近一次写的数据。
2.对其他节点之前的修改是可见(已同步)且确定的,并且新的写入建立在已经达成同步的基础上。
结论:Zookeeper写入是强一致性,读取是顺序一致性。

参考博客:
https://blog.csdn.net/weixin_47727457/article/details/106439452
https://blog.csdn.net/chao2016/article/details/81149674
https://www.cnblogs.com/mkl34367803/p/10704517.html

原文地址:https://www.cnblogs.com/jelly12345/p/15603515.html