Zookeeper 一致性的保证

1,539 阅读9分钟

ZooKeeper 使用了一种叫 ZAB(ZooKeeper Atomic Broadcast,ZooKeeper 原子消息广播协议)的算法协议进行广播和选举。ZAB协议具体分为2个部分:

ZAB协议主体内容

消息广播阶段

image.png

读请求由收到请求的节点自己处理,写数据的机制是客户端把写请求发送到leader节点上(如果发送的是follower节点,follower节点会把写请求转发到leader节点),leader节点会把数据通过proposal请求发送到所有节点(包括自己),所有到节点接受到数据以后都会写到自己到本地磁盘(log)上面,写好了以后会发送一个ack请求给leader,leader只要接受到过半的节点发送ack响应回来,就会发送commit消息给各个节点(leader自己先commit),各个节点就会把消息放入到内存中(放内存是为了保证高性能),该数据就会用户可见了。

从上面可以看出zk也是采用WAL机制

崩溃恢复阶段

下面的几种情况都会进入崩溃恢复阶段:

  • 初始化集群,刚刚启动的时候
  • Leader 崩溃,因为故障宕机
  • Leader 失去了半数的机器支持,与集群中超过一半的节点断连。

崩溃阶段还包括数据同步操作,同步集群中最新的数据,保证集群数据的一致性。当超过半数的follower和leader完成了状态(数据)同步时,则进入消息广播阶段

选举流程
1.Leader崩溃后,各个节点状态变更为Looking

2.各个Server节点都会发出一个投票,要求其他服务器投自己,参与选举

3.集群接收来自各个服务器的投票,开始投票和选举。投票过程对比的是Zxid,谁的Zxid大谁就是Leader(zxid表示事务id最大,数据最新)。如果Zxid一样,谁的myid(就是zoo.cfg中的myid)大,谁是Leader。此外投票过程只要有节点收到了半数票数后,就是新的Leader。

4.选出新Leader后,Leader会发送一个同步请求,所有Follower同步新Leader的数据

主要概念

Zookeeper 服务器角色

Zookeeper集群中,有Leader、Follower和Observer三种角色

「Leader」

Leader服务器是整个ZooKeeper集群工作机制中的核心,其主要工作:

事务请求的唯一调度和处理者,保证集群事务处理的顺序性

集群内部各服务的调度者

「Follower」

Follower服务器是ZooKeeper集群状态的跟随者,其主要工作:

  • 处理客户端非事务请求,转发事务请求给Leader服务器
  • 参与事务请求Proposal的投票
  • 参与Leader选举投票

「Observer」

Observer是3.3.0 版本开始引入的一个服务器角色,它充当一个观察者角色——观察ZooKeeper集群的最新状态变化并将这些状态变更同步过来。其工作:

  • 处理客户端的非事务请求,转发事务请求给 Leader 服务器
  • 不参与任何形式的投票

节点工作状态

服务器具有四种状态,分别是 LOOKING、FOLLOWING、LEADING、OBSERVING。

  • 1.LOOKING:寻找Leader状态。当服务器处于该状态时,它会认为当前集群中没有 Leader,因此需要进入 Leader 选举状态。
  • 2.FOLLOWING:跟随者状态。表明当前服务器角色是Follower。
  • 3.LEADING:领导者状态。表明当前服务器角色是Leader。
  • 4.OBSERVING:观察者状态。表明当前服务器角色是Observer。

znode的4种类型

根据节点的生命周期,znode可以分为4种类型,分别是持久节点(PERSISTENT)、持久顺序节点(PERSISTENT_SEQUENTIAL)、临时节点(EPHEMERAL)、临时顺序节点(EPHEMERAL_SEQUENTIAL)

持久节点(PERSISTENT)

这类节点被创建后,就会一直存在于Zk服务器上。直到手动删除。

持久顺序节点(PERSISTENT_SEQUENTIAL)

它的基本特性同持久节点,不同在于增加了顺序性。父节点会维护一个自增整性数字,用于子节点的创建的先后顺序。

临时节点(EPHEMERAL)

临时节点的生命周期与客户端的会话绑定,一旦客户端会话失效(非TCP连接断开),那么这个节点就会被自动清理掉。zk规定临时节点只能作为叶子节点。

临时顺序节点(EPHEMERAL_SEQUENTIAL)

基本特性同临时节点,添加了顺序的特性。

Watcher监听机制的工作原理

ZooKeeper的Watcher机制主要包括客户端线程、客户端 WatcherManager、Zookeeper服务器三部分。

客户端向ZooKeeper服务器注册Watcher的同时,会将Watcher对象存储在客户端的WatchManager中。

当zookeeper服务器触发watcher事件后,会向客户端发送通知, 客户端线程从 WatcherManager 中取出对应的 Watcher 对象来执行回调逻辑。

Watcher特性总结

  • 「一次性:」 一个Watch事件是一个一次性的触发器。一次性触发,客户端只会收到一次这样的信息。
  • 「异步的:」 Zookeeper服务器发送watcher的通知事件到客户端是异步的,不能期望能够监控到节点每次的变化,Zookeeper只能保证最终的一致性,而无法保证强一致性。
  • 「轻量级:」 Watcher 通知非常简单,它只是通知发生了事件,而不会传递事件对象内容。
  • 「客户端串行:」 执行客户端 Watcher 回调的过程是一个串行同步的过程。
  • 注册 watcher用getData、exists、getChildren方法
  • 触发 watcher用create、delete、setData方法

应用场景

分布式锁

juejin.cn/post/713636…

集群协调管理

dubbo配置中心

配置管理

命名服务

如何保证数据一致性

顺序一致性

聊一聊ZooKeeper的顺序一致性 time.geekbang.org/column/arti…

需要了解事务ID,即zxid。ZooKeeper的在选举时通过比较各结点的zxid和机器ID选出新的主结点的。zxid由Leader节点生成,有新写入事件时,Leader生成新zxid并随提案一起广播,每个结点本地都保存了当前最近一次事务的zxid,zxid是递增的,所以谁的zxid越大,就表示谁的数据是最新的。

ZXID的生成规则如下:

ZXID有两部分组成:

  • 任期:完成本次选举后,直到下次选举前,由同一Leader负责协调写入;
  • 事务计数器:单调递增,每生效一次写入,计数器加一。

ZXID的低32位是计数器,所以同一任期内,ZXID是连续的,每个结点又都保存着自身最新生效的ZXID,通过对比新提案的ZXID与自身最新ZXID是否相差“1”,来保证事务严格按照顺序生效的。

那些没有收到commit消息的follower怎么最终和leader数据保持一致?

由于leader和follower的数据通信是FIFO队列,保证顺序的,所以在一下次leader请求之前,follower肯定要处理之前的commit请求,比如中间超时断联了 重新连接后,和leader重新同步数据,会严格按照顺序

leader执行commit了,还没来得及给follower发送commit的时候,leader宕机了,这个时候如何保证数据一致性?

当leader宕机以后,ZooKeeper会选举出来新的leader,新的leader启动以后要到磁盘上面去检查是否存在没有commit的消息,如果存在,就继续检查看其他follower有没有对这条消息进行了commit,如果有过半节点对这条消息进行了ack,但是没有commit,那么新对leader要完成commit的操作。

客户端把消息写到leader了,但是leader还没发送proposal消息给其他节点,这个时候leader宕机了,leader宕机后恢复的时候此消息又该如何处理?

客户端把消息写到leader了,但是leader还没发送portal消息给其他节点,这个时候leader宕机了,这个时候对于用户来说,这条消息是写失败的。假设过了一段时间以后leader节点又恢复了,不过这个时候角色就变为了follower了,它在检查自己磁盘的时候会发现自己有一条消息没有进行commit,此时就会检测消息的编号,消息是有编号的,由高32位和低32位组成,高32位是用来体现是否发生过leader切换的,低32位就是展示消息的顺序的。这个时候当前的节点就会根据高32位知道目前leader已经切换过了,所以就把当前的消息删除,然后从新的leader同步数据,这样保证了数据一致性。

同一个客户端,先发出写请求,leader将写请求事务广播给follow,如果半数follow成功,但是发出请求的follow没有成功,按照半数即成功的原理,leader会返回写操作成功,此时该客户端再读取数据,导致读取到的是旧的值,是不是不符合同一个session写后读的保证?

zookeeper 的读写一致不是在 server 做的,而是 server & client 配合的;client 会记录它见过的最大的 zxid (在你的场景下,就是这条写入 的 zxid),读取的时候,如果 server 发现 这条 zxid 比 server 端的最大 zxid 大,则拒绝,client 会自动重连到其他server(还在同一个 session) —— 最终会落到有新数据的 server 上,因为半数已经同意;

多个客户端读到的数据不一致

zookeeper不保证读一致性,因为zookeeper写入成功的条件是一半node成功即成功,所以客户端连接到的node是失败的node则是老数据。如果要保证读到的数据是最新的,读取之前要使用sync方法,强行和主node保持一致。

zookeeper的优缺点

基于 ZAB 算法,ZooKeeper 集群保证数据更新的一致性,并通过集群方式保证 ZooKeeper 系统高可用。但是 ZooKeeper 系统中所有服务器都存储相同的数据,也就是数据没有分片存储,因此不满足分区耐受性。

参考www.cnblogs.com/zz-ksw/p/12…

zookeeper的持久化机制

和redis类似,有log和snapshot两个磁盘文件,log记录事务日志即每次写请求的日志,snapshot记录某一时刻的数据快照。

  • ZK 会持久化到磁盘的文件有两种:log 和 snapshot
  • log 负责记录每一个写请求
  • snapshot 负责对当前整个内存数据进行快照
  • 恢复数据的时候,会先读取最新的 snapshot 文件
  • 然后在根据 snapshot 最大的 zxid 去搜索符合条件的 log 文件,再通过逐条读取写请求来恢复剩余的数据
    写请求的流程:proposal->记录log->commit->更新内存

www.51cto.com/article/625…