3、Zookeeper数据同步及脑裂

1 引言

2 读写机制

3 数据同步

3.1 两阶段提交 & 过半写机制

3.2 ZAB崩溃恢复机制

3.3 ZAB恢复中删除数据机制

4 脑裂问题

4.1  什么是脑裂

4.2 zookeeper脑裂原因

4.3 zookeeper解决脑裂

1 引言


Zookeeper的核心机制是原子广播,这个机制保证了各个Server之间的数据同步,实现这个机制的协议叫做Zab协议。

Zab协议有两种模式,分别是恢复模式(选主)广播模式(同步)当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和 leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。

为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上 了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个 新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数

深入分析ZooKeeper的实现原理

2 读写机制


在ZooKeeper的选举中,如果过半的节点都选一个节点为leader的话,那么这个节点就会是leader节点,也就是因为这个原因,ZooKeeper集群,只要有过半的节点是存活的,那么这个ZooKeeper就可以正常的提供服务。比如有5个ZooKeeper节点,其中有2个节点宕机了,这个时候还有3个节点存活,存活个数超过半数,此时集群还是正常提供服务,所以ZooKeeper集群本生是没有高可用问题的。又因为存活的判断依据是超过半数,所以我们一般搭建ZooKeeper集群的时候,都使用奇数台,这样会比较节约机器,比如我们安装一个6台的ZooKeeper集群,如果宕机了3台就会导致集群不可用,因为这个时候存活的节点数没有超过半数了,所以6台和5台的效果是一样的,我们用5台比较合适。

对应一个ZooKeeper集群,我们可能有多个客户端,客户端能任意连接其中一台ZooKeeper节点,但是所有的客户端都只能往leader节点上面去写数据,所有的客户端能从所有的节点上面读取数据。如果有客户端连接的是follower节点,然后往follower上发送了写数据的请求,这个时候follower就会把这个写请求转发给leader节点处理。

leader接受到写请求就会往其他节点(包括自己)同步数据,如果过半的节点接受到消息后发送回来ack消息,那么leader节点就对这条消息进行commit,commit后该消息就对用户可见了。因为需要过半的节点发送ack后,leader才对消息进行commit,这个时候会有一个问题,如果集群越大,那么等待过半节点发送回来ack消息这个过程就需要越久,也就是说节点越多虽然会增加集群的读性能,但是会影响到集群的写性能,所以我们一般建议ZooKeeper的集群规模在3到5个节点左右。为了解决这个问题,后来的ZooKeeper中增加了一个observer 的角色,这个节点不参与投票,只是负责同步数据。比如我们leader写数据需要过半的节点发送ack响应,这个observer节点是不参与过半的数量统计的。它只是负责从leader同步数据,然后提供给客户端读取,所以引入这个角色目的就是为了增加集群读的性能,然后不影响集群的写性能。用户搭建集群的时候可以自己设置该角色。

3 数据同步


参考文章ZAB协议

3.1 两阶段提交 & 过半写机制

1.leader接受到消息请求后,将消息赋予一个全局唯一的64位自增id,叫Zxid,通过zxid的大小比较即可实现因果有序这个特征。
2.leader为每个follower准备一个FIFO队列(通过TCP来实现,以实现了全局有序这个特点)将带有Zxid的消息作为一个提案(proposal)分发给所有的follower。
3.当follower接受到proposal,先把proposal写到磁盘,写入成功以后在向leader回复一个ACK。
4.当leader接受到合法数量(超过半数节点)的ACK命令后,leader就会向这些follower发送commit命令,同时会在本地执行消息。
5.当follower收到消息的commit命令后,会提交该消息。
6.leader的投票过程,不需要Observer的ACK,也就是说Observer不需要参与投票过程,但是Observer必须要同步Leader的数据从而在处理请求的时候,保证数据的一致性。

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

如果ZooKeeper要想保证数据一致性,就需要考虑如下两个情况

  • 情况一:leader执行commit了,还没来得及给follower发送commit的时候,leader宕机了,这个时候如何保证消息一致性?
  • 情况二:客户端把消息写到leader了,但是leader还没发送proposal消息给其他节点,这个时候leader宕机了,leader宕机后恢复的时候此消息又该如何处理?

3.2 ZAB崩溃恢复机制

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

3.3 ZAB恢复中删除数据机制

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

4 脑裂问题


4.1  什么是脑裂

简单点来说,脑裂(Split-Brain) 就是比如当你的 cluster 里面有两个节点,它们都知道在这个 cluster 里需要选举出一个 master。那么当它们两个之间的通信完全没有问题的时候,就会达成共识,选出其中一个作为 master。但是如果它们之间的通信出了问题,那么两个结点都会觉得现在没有 master,所以每个都把自己选举成 master,于是 cluster 里面就会有两个 master。

对于Zookeeper来说有一个很重要的问题,就是到底是根据一个什么样的情况来判断一个节点死亡down掉了?在分布式系统中这些都是有监控者来判断的,但是监控者也很难判定其他的节点的状态,唯一一个可靠的途径就是心跳,Zookeeper也是使用心跳来判断客户端是否仍然活着

使用ZooKeeper来做Leader HA基本都是同样的方式:每个节点都尝试注册一个象征leader的临时节点,其他没有注册成功的则成为follower,并且通过watch机制 (这里有介绍) 监控着leader所创建的临时节点,Zookeeper通过内部心跳机制来确定leader的状态,一旦leader出现意外Zookeeper能很快获悉并且通知其他的follower,其他flower在之后作出相关反应,这样就完成了一个切换,这种模式也是比较通用的模式,基本大部分都是这样实现的。但是这里面有个很严重的问题,如果注意不到会导致短暂的时间内系统出现脑裂,因为心跳出现超时可能是leader挂了,但是也可能是zookeeper节点之间网络出现了问题,导致leader假死的情况,leader其实并未死掉,但是与ZooKeeper之间的网络出现问题导致Zookeeper认为其挂掉了然后通知其他节点进行切换,这样follower中就有一个成为了leader,但是原本的leader并未死掉,这时候client也获得leader切换的消息,但是仍然会有一些延时,zookeeper需要一个一个通知,这时候整个系统就很混乱可能有一部分client已经通知到了连接到新的leader上去了,有的client仍然连接在老的leader上,如果同时有两个client需要对leader的同一个数据更新,并且刚好这两个client此刻分别连接在新老的leader上,就会出现很严重问题。

这里做下小总结:
假死:由于心跳超时(网络原因导致的)认为leader死了,但其实leader还存活着。
脑裂:由于假死会发起新的leader选举,选举出一个新的leader,但旧的leader网络又通了,导致出现了两个leader ,有的客户端连接到老的leader,而有的客户端则连接到新的leader。

4.2 zookeeper脑裂原因

主要原因是Zookeeper集群和Zookeeper client判断超时并不能做到完全同步,也就是说可能一前一后,如果是集群先于client发现,那就会出现上面的情况。同时,在发现并切换后通知各个客户端也有先后快慢。一般出现这种情况的几率很小,需要leader节点与Zookeeper集群网络断开,但是与其他集群角色之间的网络没有问题,还要满足上面那些情况,但是一旦出现就会引起很严重的后果,数据不一致。

4.3 zookeeper解决脑裂

要解决Split-Brain脑裂的问题,一般有下面几种种方法:

  • Quorums (法定人数) 方式: 比如3个节点的集群,Quorums = 2, 也就是说集群可以容忍1个节点失效,这时候还能选举出1个lead,集群还可用。比如4个节点的集群,它的Quorums = 3,Quorums要超过3,相当于集群的容忍度还是1,如果2个节点失效,那么整个集群还是无效的。这是zookeeper防止"脑裂"默认采用的方法。
  • Redundant communications (冗余通信)方式:集群中采用多种通信方式,防止一种通信方式失效导致集群中的节点无法通信。
  • Fencing (共享资源) 方式:比如能看到共享资源就表示在集群中,能够获得共享资源的锁的就是Leader,看不到共享资源的,就不在集群中。
  • 仲裁机制方式。
  • 启动磁盘锁定方式。

zooKeeper默认采用了Quorums这种方式来防止"脑裂"现象。即只有集群中超过半数节点投票才能选举出Leader。这样的方式可以确保leader的唯一性,要么选出唯一的一个leader,要么选举失败。在zookeeper中Quorums作用如下:

  • 集群中最少的节点数用来选举leader保证集群可用。
  • 通知客户端数据已经安全保存前集群中最少数量的节点数已经保存了该数据。一旦这些节点保存了该数据,客户端将被通知已经安全保存了,可以继续其他任务。而集群中剩余的节点将会最终也保存了该数据。

假设某个leader假死,其余的followers选举出了一个新的leader。这时,旧的leader复活并且仍然认为自己是leader,这个时候它向其他followers发出写请求也是会被拒绝的。因为每当新leader产生时,会生成一个epoch标号(标识当前属于那个leader的统治时期),这个epoch是递增的,followers如果确认了新的leader存在,知道其epoch,就会拒绝epoch小于现任leader epoch的所有请求。那有没有follower不知道新的leader存在呢,有可能,但肯定不是大多数,否则新leader无法产生。Zookeeper的写也遵循quorum机制,因此,得不到大多数支持的写是无效的,旧leader即使各种认为自己是leader,依然没有什么作用。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值