Zookeeper的特点就是,分布式,高可用,自带容错,所有节点读到的数据都是一致的。使用的场景通常是微服务的注册中心,或者一些分布式的开源软件用来保存元数据,或者监测生命状态。
这些使用场景默认Zookeeper永远是可用的,而且去Zookeeper集群旗下的每家分号,获取的数据都是一样的,通常情况下也确实如此。
也就是说可用性和一致性是Zookeeper的关键特性,作为一个消息的中间商,做了一个可靠的信息传递和存储的角色。
但是了解下Zookeeper的ZAB协议,特别是写入部分和读部分,发现了一点细节,Zookeeper不能保证永远读到最新的数据,这里简述下Zookeeper读写过程、
写过程
Leader接收Client的写请求,广播给其他Follower节点,其他节点将消息加入待写队列,向Leader发送成功消息,过半的Follower同意后,Leader向所有节点发送提交消息,Follower会落实写请求
读过程
Client直接访问一台 Zookeeper获取信息
也就是说,如果在写的过程中,过半的follower同意了,这条消息通过写入,但有一台Zookeeper和Leader无法通信了,或者因为磁盘,内存等原因拒绝写入了。此时一个client来这个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,但能保证在没有更新后达成一致。
顺序一致性第一句比较好理解,第二句就比较抽象了,字看的懂,但还是不知道具体说啥,经过查阅资料
来看这张水印重重的图,
最上面的(a),
两个进程p1和p2,p1先写了x=4,然后进行读操作,读了y=2,
p2写了y=2,然后进行读操作,读了x=0。
从全局时钟来看,p2对x的读取在P1的写操作之后,但是数值却是旧的,也就是说这个系统中两个进程并不是每个时刻都能保持数值的一致,不满足强一致性。
但是如果从进程角度来看,我p2执行的操作,与p1并不冲突,如果p2先执行,p1后执行,
p2写了y=2,读到x=0,之后p1才写了x=4,并且读到的y确实也是2,
那他就符合顺序一致性,而且也确实读到了某个数据的最近一次写的数据。
(b)图b满足强一致性,因为每个读操作都读到了该变量的最新写的结果,同时两个进程看到的操作顺序与全局时钟的顺序一样,都是Write(y,2) , Read(x,4) , Write(x,4), Read(y,2)
©可以看出,假如p2在先,那么p1读到的y值应该是2而不是0。假如p1在先,那么p2读到的x值应该应该是4而不是0。所以他不符合顺序一致性,更不符合强一致性。
再说下Java内存模型中顺序一致性,如果对多线程并发有理解,可以结合下来理解
顺序一致性内存模型是一个被计算机科学家理想化了的理论参考模型,它为程序员提供了极强的内存可见性保证。顺序一致性内存模型有两大特性:
一个线程中的所有操作必须按照程序的顺序来执行。
(不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见。
这里的顺序一致性,讲的是一种多线程并发执行下理想情况,包含两种要求
1.线程中的操作必须按照程序的顺序执行,也就是说,不能自己自作主张,更换执行顺序
2.线程中的操作是原子性的,执行了就是执行了,没执行就是没执行,不存在中间状态,而且一旦执行,其他变量应该立刻可见。
联系到zookeeper,说点结论
1.各节点的数据更新必须按照顺序进行。
2.数据写入执行情况,数据版本应对其他节点可见(leader能知道写入是否成功)。
结合以上,你会发现,zookeeper并不是最终一致性,而是顺序一致性。
1.最终一致性的特点是,无法保证任意节点在同一时间某份数据是相同的,但是最终在没有新的更新时会达成一致。
而Zookeeper所有节点的数据版本都是递增的,可能会有某个节点因故障版本低于大多数,但是是有序的,不会出现各自增长的情况。
比如,Zookeeper节点可能会出现4台数据是version 5,一台数据是version4。但是不会是5台机器各自更新。
所以这里对顺序一致性的定义是
1.任何一次读都能读到某个数据的最近一次写的数据。
2.对其他节点之前的修改是可见(已同步)且确定的,并且新的写入建立在已经达成同步的基础上。
结论:Zookeeper写入是强一致性,读取是顺序一致性。
然后这只是基于现有的资料的一点思考,如果以后发现有不对的,随时修改。
参考:
Zookeeper并不保证读取的是最新数据
强一致性、顺序一致性、弱一致性和共识
深入理解Java内存模型(三)——顺序一致性
ZooKeeper和CAP理论及一致性原则
分布式架构之Consistency(一致性、强一致性,弱一致性,顺序一致性,最终一致性)
============
作者:花灯渔火
版权归作者所有,转载请注明出处