整理一些关于Zookeeper相关的知识。
ZooKeeper是一个开放源码的分布式协调服务,它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
分布式应用程序可以基于Zookeeper实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Leader选举、分布式锁和分布式队列等功能。
Zookeeper保证了如下分布式一致性特性:
客户端的读请求可以被集群中的任意一台机器处理,如果读请求在节点上注册了监听器,这个监听器也是由所连接的zookeeper机器来处理。对于写请求,会统一由Leader接收并处理,广播给其他zookeeper机器并且达成一致后,请求才会返回成功。因此,随着zookeeper的集群机器增多,读请求的吞吐会提高但是写请求的吞吐会下降。
有序性是zookeeper中非常重要的一个特性,所有的更新都是全局有序的,每个更新都有一个唯一的时间戳,这个时间戳称为zxid(Zookeeper Transaction Id)。而读请求只会相对于更新有序,也就是读请求的返回结果中会带有这个zookeeper最新的zxid。
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
我们知道ACID中事务的一致性是指事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行前后,数据库都必须处于一致性状态。也就是说,事务的执行结果必须是使数据库从一个一致性状态转变到另一个一致性状态。
和ACID中的一致性不同,分布式环境中的一致性是指数据在多个副本之间是否能够保持一致的特性。
分布式系统中,数据一般会存在不同节点的副本中,如果对第一个节点的数据成功进行了更新操作,而第二个节点上的数据却没有得到相应更新,这时候读取第二个节点的数据依然是更新前的数据,即脏数据,这就是分布式系统数据不一致的情况。 在分布式系统中,如果能够做到针对一个数据项的更新操作执行成功后,所有的用户都能读取到最新的值,那么这样的系统就被认为具有强一致性(或严格的一致性)。
可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果,如果超过了这个时间范围,那么系统就被认为是不可用的。
“有限的时间内”是在系统的运行指标,不同系统会有差别。例如搜索引擎通常在0.5秒内需要给出用户检索结果。
“返回结果”是可用性的另一个重要指标,它要求系统完成对用户请求的处理后,返回一个正常的响应结果,要明确的反映出对请求处理的成功或失败。如果返回的结果是系统错误,比如"OutOfMemory"等报错信息,则认为此时系统是不可用的。
一个分布式系统中,节点组成的网络本来应该是连通的。然而可能因为某些故障,使得有些节点之间不连通了,整个网络就分成了几块区域,而数据就散布在了这些不连通的区域中,这就叫分区。
当你一个数据项只在一个节点中保存,那么分区出现后,和这个节点不连通的部分就访问不到这个数据了。这时分区就是无法容忍的。
提高分区容忍性的办法就是一个数据项复制到多个节点上,那么出现分区之后,这一数据项仍然能在其他区中读取,容忍性就提高了。然而,把数据复制到多个节点,就会带来一致性的问题,就是多个节点上面的数据可能是不一致的。要保证一致,每次写操作就都要等待全部节点写成功,而这等待又会带来可用性的问题。
总的来说就是,数据存在的节点越多,分区容忍性越高,但要复制更新的数据就越多,一致性就越难保证。为了保证一致性,更新所有节点数据所需要的时间就越长,可用性就会降低
在分布式系统中,CAP不可能完全都保证,而由于系统硬件原因或系统之间的网络原因肯定会导致网络丢包,所以分区容错性是一定要保证的,接下来就是要在C和A之间选择了。
C是一致性,分布式数据一致性,zk保证的就是CP。
A是可用性,Eureka保证的是可用性,实现的AP。
BASE理论是对CAP理论的延伸,思想是即使无法做到强一致性(CAP的一致性就是强一致性),但可以采用适当的采取弱一致性,即最终一致性。
BASE理论是基本可用(Basically Available),软状态(soft state),最终一致性(Eventually consistent)的简称。
Base 理论是对 CAP 中一致性和可用性权衡的结果,其来源于对大型互联网分布式实践的总结,是基于 CAP 定理逐步演化而来的。其核心思想是:
即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。
Basically Available(基本可用)
什么是基本可用呢?假设系统,出现了不可预知的故障,但还是能用,相比较正常的系统而言:
Soft state(软状态)
什么是软状态呢?相对于原子性而言,要求多个节点的数据副本都是一致的,这是一种 “硬状态”。
软状态指的是:允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。
Eventually consistent(最终一致性)
这个比较好理解了哈。
上面说软状态,然后不可能一直是软状态,必须有个时间期限。在期限过后,应当保证所有副本保持数据一致性。从而达到数据的最终一致性。这个时间期限取决于网络延时,系统负载,数据复制方案设计等等因素。
稍微官方一点的说法就是:
系统能够保证在没有其他新的更新操作的情况下,数据最终一定能够达到一致的状态,因此所有客户端对系统的数据访问最终都能够获取到最新的值。
常见的数据一致性级别有如下几种:
(1)强一致性(strong consistency):任何时刻,任何用户或节点都可以读到最近一次成功更新的副本数据。强一致性是程度要求最高的一致性要求。
(2)单调一致性(monotonic consistency):任何时刻,任何用户一旦读到某个数据在某次更新后的值,这个用户不会再读到比这个值更旧的值。
(3)会话一致性(session consistency):任何用户在某一次会话内一旦读到某个数据在某次更新后的值,这个用户在这次会话过程中不会读到比这个值更旧的值。
(4)最终一致性(eventual consistency):一旦数据更新成功,各个副本上的数据最终达到完全一致的状态,但达到完全一致状态所需要的时间不能保障。对于最终一致性系统而言,一个用户只要始终读取某一个副本的数据,则可以实现类似单调一致性的效果,但用户一旦更换读取的副本,则无法保证任何一致性。
(5)弱一致性(week consistency):一旦某个更新成功,用户无法在一个确定的时间内读到这次更新的值,且及时在某个副本上读到了新的值,也不能保证在其他副本上可以读到新的值。
采用 ZAB协议,类似 2PC 两阶段提交保证数据一致性。
ZAB 协议规定所有的事务请求都由 Leader 统一接收并处理,并采用 ZAB 的原子广播协议,将服务器数据的状态变更以 事务proposal (事务提议)的形式广播到所有的Follower进程上去。
具体:Leader 将客户端的写请求转换成事务请求,然后将该请求写入事务日志并向所有的 Follower 发送广播,Follower 收到消息后会首先将其以事务日志的方式写入本地磁盘中,写入成功后向 Leader 反馈一个 Ack 响应消息。Leader 收到过半的 Ack 后,即认为消息发送成功,接着 Leader 向所有 Follower 广播 commit 消息,同时自身也会完成事务提交。Follower 接收到 commit 消息后,会将上一条事务提交。
Zookeeper中数据副本的同步方式与二段提交相似,但是却又不同。二段提交要求协调者必须等到所有的参与者全部反馈ACK确认消息后,再发送commit消息。要求所有的参与者要么全部成功,要么全部失败。二段提交会产生严重的阻塞问题。
Leader 服务器与每一个 Follower 服务器之间都维护了一个单独的 FIFO 消息队列进行收发消息,使用队列消息可以做到异步解耦。Leader 和 Follower 之间只需要往队列中发消息即可。如果使用同步的方式会引起阻塞,性能要下降很多。
Zookeeper Atomic Broadcast,Zookeeper 原子广播协议 ,Zookeeper 是通过 Zab 协议来保证分布式事务的最终一致性。
Zookeeper 客户端会随机的链接到 zookeeper 集群中的一个节点,如果是读请求,就直接从当前节点中读取数据;如果是写请求,那么节点就会向 Leader 提交事务,Leader 接收到事务提交,会广播该事务,只要超过半数节点写入成功,该事务就会被提交。
Zab 协议的特性:
1)Zab 协议需要确保那些已经在 Leader 服务器上提交(Commit)的事务最终被所有的服务器提交。2)Zab 协议需要确保丢弃那些只在 Leader 上被提出而没有被提交的事务。
两阶段提交(tow phase commit):1、预提交,2、ack,3、提交。
leader 收到客户端写请求后,写入事务日志,并向所有 follower 发送欲提交请求,follower 收到后写入自己的事务日志,并返回给leader ack反馈,leader 收到过半的 ack 后,先自己提交事务,然后再向所有 follower 发送提交指令。
当客户端连接的是 follower并发送写请求时,follower 会将此请求转发至 leader,由 leader 统一处理事务请求。
选举算法最常用的是快速选举算法,其他几个都已经不建议使用。
快速选举算法适用于集群版,单机版没有。涉及到的关键词和术语,
选举轮次,低版本的领导者轮次在ZXID中的高32位,高版本的在选票信息里附加的。peerEpoch 提议的领导者的轮次
if (!backCompatibility) {//请求 > 28字节 //提议的领导者的轮次从选票信息获取 n.peerEpoch = response.buffer.getLong(); } else {//请求 = 28字节 if (LOG.isInfoEnabled()) {//向后兼容模式 LOG.info("Backward compatibility mode, server id=" + n.sid); } //提议的领导者的轮次从ZXID的高32位获取 n.peerEpoch = ZxidUtils.getEpochFromZxid(n.zxid);}
public class ZxidUtils { static public long getEpochFromZxid(long zxid) { return zxid >> 32L; } //other code}
每个节点首先会投给自己,然后节点之间交流选票(把自己的选票互相发送给其他节点),拿到其他节点发来的选票后进行选票的PK:
1、若this.epoch<rece.epoch,说明自己已经落后,修改自己的轮次=收到的选票轮次,然后清空自己的投票箱再决定投给谁,然后把自己的投票广播出去。 2、若this.epoch=rece.epoch,则比较zxid,自己的较大则不改票;若zxid相等则比较myid,若myid大则不改票,否则改为收到的选票;若this.zxid<rece.zxid,则改为收到的选票并广播。 3、this.epoch>rece.epoch,则忽略。
然后统计选票,是否有过半的机器和自己投的一样,过半则该节点被选举为leader,一旦leader被确定,集群内的各个节点角色都已确定,leader信息被广播出去,各节点修改自己的状态。
若没有过半,则继续把选票发出去。重复以上操作。