zk选举机制和分布式一致性原理

CAP定理

CAP定理是由加州大学伯克利分校Eric Brewer教授提出来的,他指出WEB服务无法同时满足一下3个属性:

一致性(Consistency) : 客户端知道一系列的操作都会同时发生(生效)
可用性(Availability) : 每个操作都必须以可预期的响应结束
分区容错性(Partition tolerance) : 即使出现单个组件无法可用,操作依然可以完成
具体地讲在分布式系统中,在任何数据库设计中,一个Web应用至多只能同时支持上面的两个属性。显然,任何横向扩展策略都要依赖于数据分区。因此,设计人员必须在一致性与可用性之间做出选择。

这个定理在迄今为止的分布式系统中都是适用的! 为什么这么说呢?
这个时候有同学可能会把数据库的2PC(两阶段提交)搬出来说话了。OK,我们就来看一下数据库的两阶段提交。
对数据库分布式事务有了解的同学一定知道数据库支持的2PC,又叫做 XA Transactions。
MySQL从5.5版本开始支持,SQL Server 2005 开始支持,Oracle 7 开始支持。
其中,XA 是一个两阶段提交协议,该协议分为以下两个阶段:

  1. 第一阶段:事务协调器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交.
  2. 第二阶段:事务协调器要求每个数据库提交数据。
     

一致性算法

1 2PC提交(RDBMS(传统关系型数据库)经常就这种机制,保证强 致 性)

2 3PC 论 3 paxos 4 Raft  5 zab

接下来我们重点讲一下zab算法,因为zk一致性算法使用的是zab

zab

1、Zab 协议
在分布式系统中实现一致性是件困难的事。
Paxos 算法可以较好的解决分布式系统的一致性,但由于复杂,在实际工程上不是很合适。
ZAB(ZooKeeper Atomic Broadcast ) 协议借鉴了 Paxos 的思想,ZAB在Paxos算法上做了重要改造,和Paxos有着明显的不同,以满足工程上的实际需求。

有序性是 Zab 协议与 Paxos 协议的一个核心区别。

Zab 的有序性主要表现在两个方面:

全局有序:如果消息 a 在消息 b 之前被投递,那么在任何一台服务器,消息 a都会在消息 b 之前被投递。
因果有序:如果消息 a 在消息 b 之前发生(a 导致了 b),并被一起发送,则 a 始终在 b 之前被执行。
2、Zab 协议分为两部分

  1. 广播(boardcast):Zab 协议中,所有的写请求都由 leader 来处理。正常工作状态下,leader 接收请求并通过广播协议来处理。
  2. 恢复(recovery):当服务初次启动,或者 leader 节点挂了,系统就会进入恢复模式,直到选出了有合法数量 follower 的新 leader,然后新 leader 负责将整个系统同步到最新状态

2.1 广播(boardcast)
广播的过程实际上是一个简化的二阶段提交过程:

1.Client会向一个Follower提交一个写请求
2.Follower将写请求发送给Leader
3.Leader接收到写请求后,将消息赋予一个全局唯一zxid,通过 zxid 的大小比较即可实现因果有序这一特性。Leader 通过每个 follower对应的队列将带有 zxid 的消息作为一个提案(proposal)广播分发给所有 follower。消息体类似(zxid-0,data)形式。
4.当 follower 接收到 proposal,先将 proposal 写到硬盘,写硬盘成功后再向 leader 回一个 ACK。
5.当 leader 接收到超过半数的 ACKs 后,leader 就向所有 follower 广播发送 COMMIT 提交命令,同时会在本地执行该消息。当 follower 收到消息的 COMMIT 命令时,就会执行提交。
6.(接收Client请求的)Follower将写请求的结果返回给Client。
2.2 恢复(recovery)
由于之前讲的 Zab 协议的广播部分不能处理 leader 挂掉的情况,Zab 协议引入了恢复模式来处理这一问题。为了使 leader 挂了后系统能正常工作,需要解决以下两个问题:

  1. 已经被处理的消息不能丢
  2. 被丢弃的消息不能再次出现

让Leader选举算法能够保证新选举出来的Leader服务器拥有集群中所有机器最高编号(ZXID最大)的事务Proposal,就可以保证这个新选举出来的Leader一定具有所有已经提交的提案。更为重要的是,如果让具有最高编号事务Proposal的机器来成为Leader,就可以省去Leader服务器检查Proposa的提交和丢弃工作这一步

ZAB 中的节点有三种状态

  1. following:当前节点是跟随者,服从 leader 节点的命令

  2. leading:当前节点是 leader,负责协调事务

  3.  election/looking:节点处于选举状态

事务ID的概念

  1. Zxid: 个 64 位的数字,低 32 位是 个简单的单调递增的计数 ,针对客户端每个事务请求数据ID,每次数据变动都会自增,计数加 ;32 位则代表 Leader 周期 epoch 的编号,每个当选产 个新的 Leader 服务 。
  2. myid: server_id  也就是zk我们配置的每个节点的id(也叫Sid:服务 的SID )
  3. epoch:可以 解为当前集群所处的 代或者周期,皇帝的 号,所以每次改朝换代,leader 变之后,都会在前 个 代的基础上加 1。这样就算旧的 leader 崩溃恢复之后,也没有 听他的 ,因为 follower 只听从当前 代的 leader 的命令。

选举成为leader的条件

  • 成为 Leader 的条件:
    1)选 epoch 最大的
    2)若 epoch 相等,选 zxid 最大的
    3)若 epoch 和 zxid 相等,选择 server_id 最大的(zoo.cfg中的myid)

节点在选举开始时,都默认投票给自己,当接收其他节点的选票时,会根据上面的 Leader条件 判断并且更改自己的选票,然后重新发送选票给其他节点。当有一个节点的得票超过半数,该节点会设置自己的状态为 Leading ,其他节点会设置自己的状态为 Following

zk投票流程

最初启动时投票

  1. 每个服务器发送一个投票(SID,ZXID),其中sid是自己的myid,初始阶段都将自己投为Leader。
  2. 接收来自其他服务器的投票。首先判断投票有效性,包括验证是否是来自Looking状态的服务器。
  3. 处理投票,针对每个投票都按以下规则与自己的投票PK,PK后依据情况是否更新投票,再发送给其他机器。
  • 优先检查ZXID,ZXID较大者优先为Leader
  • 如果ZXID相同,检查SID,SID较大者优先为Leader
  1. 统计投票:每次投票后,服务器统计所有投票,判断是否有过半的机器收到相同的投票,如果某个投票达到一半的要求,则认为该投票提出者可以成为Leader。
  2. 改变服务器状态:一旦确定了Leader,每个服务器都更新自己的状态,Leader变更为Leading,Follower变更为Following

正常情况下一旦选出一个Leader则一直会保持,除非Leader服务器宕掉☭,则再进行重新选举。

二 运行时的投票

  1. 变更状态
  2. 当Leader宕机后,余下的所非Observer的服务器都会将自己的状态变更为Looking,然后开启新的Leader选举流程。
  3. 每个服务器发出一个投票。生成(SID,ZXID)信息,注意运行期间的ZXID可能是不同的,但是在投票时都会将自己投为Leader,然后发送给其他的服务器。
  4. 接收来自各个服务器的投票
  5. 处理投票,规则与初次启动时相同。
  6. 统计投票
  7. 改变服务器状态

Zookeeper集群节点数量为什么要是奇数个?

首先需要明确zookeeper选举的规则:leader选举,要求 可用节点数量 > 总节点数量/2  。注意 是 > , 不是 ≥。

  1. 防止由脑裂造成的集群不可用
  2. 在容错能力相同的情况下,奇数台更节省资源

注:为什么规则要求 可用节点数量 > 集群总结点数量/2 ?  如果不这样限制,在集群出现脑裂的时候,可能会出现多个子集群同时服务的情况(即子集群各组选举出自己的leader), 这样对整个zookeeper集群来说是紊乱的。

换句话说,如果遵守上述规则进行选举,即使出现脑裂,集群最多也只能回出现一个子集群可以提供服务的情况(能满足节点数量> 总结点数量/2 的子集群最多只会有一个)。所以要限制 可用节点数量 > 集群总结点数量/2 。

1、防止由脑裂造成的集群不可用。采用奇数个的节点主要是出于两方面的考虑:

什么是脑裂?

         集群的脑裂通常是发生在节点之间通信不可达的情况下,集群会分裂成不同的小集群,小集群各自选出自己的master节点,导致原有的集群出现多个master节点的情况,这就是脑裂。

下面举例说一下为什么采用奇数台节点,就可以防止由于脑裂造成的服务不可用:

(1) 假如zookeeper集群有 5 个节点,发生了脑裂,脑裂成了A、B两个小集群: 

     (a) A : 1个节点 ,B :4个节点 , 或 A、B互换

     (b) A : 2个节点, B :3个节点  , 或 A、B互换

    可以看出,上面这两种情况下,A、B中总会有一个小集群满足 可用节点数量 > 总节点数量/2 。所以zookeeper集群仍然能够选举出leader , 仍然能对外提供服务,只不过是有一部分节点失效了而已。

(2) 假如zookeeper集群有4个节点,同样发生脑裂,脑裂成了A、B两个小集群:

    (a) A:1个节点 ,  B:3个节点,   或 A、B互换 

    (b) A:2个节点 , B:2个节点

    可以看出,情况(a) 是满足选举条件的,与(1)中的例子相同。 但是情况(b) 就不同了,因为A和B都是2个节点,都不满足 可用节点数量 > 总节点数量/2 的选举条件, 所以此时zookeeper就彻底不能提供服务了。

综合上面两个例子可以看出: 在节点数量是奇数个的情况下, zookeeper集群总能对外提供服务(即使损失了一部分节点);如果节点数量是偶数个,会存在zookeeper集群不能用的可能性(脑裂成两个均等的子集群的时候)。

在生产环境中,如果zookeeper集群不能提供服务,那将是致命的 , 所以zookeeper集群的节点数一般采用奇数个。

2、在容错能力相同的情况下,奇数台更节省资源。

leader选举,要求 可用节点数量 > 总节点数量/2  。注意 是 > , 不是 ≥。

举两个例子:

(1) 假如zookeeper集群1 ,有3个节点,3/2=1.5 ,  即zookeeper想要正常对外提供服务(即leader选举成功),至少需要2个节点是正常的。换句话说,3个节点的zookeeper集群,允许有一个节点宕机。

(2) 假如zookeeper集群2,有4个节点,4/2=2 , 即zookeeper想要正常对外提供服务(即leader选举成功),至少需要3个节点是正常的。换句话说,4个节点的zookeeper集群,也允许有一个节点宕机。

那么问题就来了, 集群1与集群2都有 允许1个节点宕机 的容错能力,但是集群2比集群1多了1个节点。在相同容错能力的情况下,本着节约资源的原则,zookeeper集群的节点数维持奇数个更好一些。

动态增加ZK Server/节点的解决方案:

1、ZK从3.5版本开始已经增加了动态增加节点的功能,并且是属于动态读取配置文件而不用重启全部ZK Server。

  • 通过最新版本来解决的方案,对于已经连接到ZK Server的客户端,可以采用代码灰度更新的方案来逐步更新掉Connect的服务器IP列表。而不用影响现有的业务完整性。
  • 当然,对于客户端的连接字符串,可以采用远程地址的方式,比如访问一条固定的URL,返回服务器IP列表,当有新的服务器IP列表更新时,将通知全部的客户端去更新。这样可以解决采用代码灰度更新的问题。

zookeeper脑裂(Split-Brain)问题

什么是脑裂?

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

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

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

总结:

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

zookeeper脑裂解决方案:

为了避免出现脑裂,可采用下面的预防措施: 

  1. 添加冗余的心跳线,例如双线条线。尽量减少“裂脑”发生机会。 
  2. 启用磁盘锁。正在服务一方锁住共享磁盘,“裂脑”发生时,让对方完全“抢不走”共享磁盘资源。但使用锁磁盘也会有一个不小的问题,如果占用共享盘的一方不主动“解锁”,另一方就永远得不到共享磁盘。现实中假如服务节点突然死机或崩溃,就不可能执行解锁命令。后备节点也就接管不了共享资源和应用服务。于是有人在HA中设计了“智能”锁。即,正在服务的一方只在发现心跳线全部断开(察觉不到对端)时才启用磁盘锁。平时就不上锁了。 
  3. 设置仲裁机制。例如设置参考IP(如网关IP),当心跳线完全断开时,2个节点都各自ping一下 参考IP,不通则表明断点就出在本端,不仅“心跳”、还兼对外“服务”的本端网络链路断了,即使启动(或继续)应用服务也没有用了,那就主动放弃竞争,让能够ping通参考IP的一端去起服务。更保险一些,ping不通参考IP的一方干脆就自我重启,以彻底释放有可能还占用着的那些共享资源。

如何防止HA集群脑裂

一般采用2个方法
1. 仲裁
 当两个节点出现分歧时,由第3方的仲裁者决定听谁的。这个仲裁者,可能是一个锁服务,一个共享盘或者其它什么东西。
2. fencing
 当不能确定某个节点的状态时,通过fencing把对方干掉,确保共享资源被完全释放,前提是必须要有可靠的fence设备。
理想的情况下,以上两者一个都不能少。
但是,如果节点没有使用共享资源,比如基于主从复制的数据库HA,我们也可以安全的省掉fence设备,只保留仲裁。而且很多时候我们的环境里也没有可用的fence设备,比如在云主机

扫一扫加入大数据公众号和技术交流群,了解更多大数据技术,还有免费资料等你哦

扫一扫加入大数据公众号和技术交流群,了解更多大数据技术,还有免费资料等你哦

扫一扫加入大数据公众号和技术交流群,了解更多大数据技术,还有免费资料等你哦

 

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿华田512

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值