split-brain
指在一个高可用(HA)系统中,当联系着的两个节点断开联系时,本来为一个整体的系统,分裂为两个独立节点,这时两个节点开始争抢共享资源,结果会导致系统混乱,数据损坏。
在详细介绍脑裂之前,先来说下假死。在分布式系统中,大多数都是 Master-Slave
模式,为了保证高可用都有监控器通过心跳监控当前 master
状态。监控器发现 master
挂掉后会立刻启动备用 master
保证系统的高可用。监控器是根据心跳超时来判断 master
挂掉,但若是 master
和监控器之间网络问题也可能导致超时。这种情况下 master
并未死掉,称为假死。但此时监控器已经通知备用 master
称为 active master
,原本 master
心跳又正常了,此时系统中会存在两个 master
。当 slave
面对两个 active master
时,你说该怎么办呢?
Zookeeper
Zookeeper
集群是主从模式当然也是会遇到脑裂的情况:master
切换时,client
也获得 master
切换的消息,但是仍然会有一些延时,Zookeeper
需要通讯需要一个一个通知。此时老的 master 活过来了,这时候整个系统就很混乱可能有一部分 client
已经通知到了连接到新的 master 上去了,有的 client
仍然连接在老的 master 上如果同时有两个 client
需要对 master
的同一个数据更新并且刚好这两个 client
此刻分别连接在新老的 master
上,就会出现很严重问题。
Zookeeper
解决 split-Brain
的方法有三种:
-
Quorums
(ˈkwôrəm 法定人数) :比如3个节点的集群,Quorums = 2, 也就是说集群可以容忍1个节点失效,这时候还能选举出1个lead,集群还可用。比如4个节点的集群,它的Quorums = 3,Quorums要超过3,相当于集群的容忍度还是1,如果2个节点失效,那么整个集群还是无效的(live_node >= total_node/2+1
) -
Redundant communications
:冗余通信的方式,集群中采用多种通信方式,防止一种通信方式失效导致集群中的节点无法通信(心跳 HA
) -
Fencing
, 共享资源的方式:比如能看到共享资源就表示在集群中,能够获得共享资源的锁的就是Leader
,看不到共享资源的,就不在集群中。
ZooKeeper
默认采用了 Quorums
这种方式,即只有集群中超过半数节点投票才能选举出 Leader
:假设某个 leader
假死,其余的 followers
选举出了一个新的 leader。这时,旧的 leader 复活并且仍然认为自己是 leader
,这个时候它向其他 followers
发出写请求是会被拒绝的。因为每当新 leader 产生时,会生成一个 epoch
,这个 epoch是递增,followers
如果确认了新的 leader 存在,知道其 epoch
,就会拒绝 epoch小于现任 leader epoch 的所有请求。那有没有 follower
不知道新的 leader 存在呢,有可能,但肯定不是大多数,否则新 leader无法产生。Zookeeper
的写也遵循 quorum
机制,因此,得不到大多数支持的写是无效的,旧 leader 即使各种认为自己是 leader
,依然没有什么作用。
这样的方式可以确保 leader
的唯一性:
要么选出唯一的一个
leader
,要么选举失败。
集群中Zookeeper
节点不是越多越好,多了数据同步时间越长,脑裂的情况越容易出现
Hadoop
active NN
会在 zookeeper
建立临时节点,standby NN
会 watch
临时节点,当 active NN
挂掉时临时节点被删除,standby NN
会收到临时节点删除的通知,此时切换成 active NN
;以上就是 NN
的切换过程(HA)。 当 active NN
出现假死时,自然就会出现 Split-Brain
。但不同于 Zookeeper
面对 slave
,Hadoop
是面对 DN
、共享系统、Client
。
DN
先说下脑裂发生,DN
的解决方案:确保只有一个 NN 能命令 DN
- 每个
NN
改变状态的时候,向DN
发送自己的状态和一个序列号 DN
在运行过程中维护此序列号,当failover
时,新的NN
在返回DN
心跳时会返回自己的active
状态和一个更大的序列号(DN
会同事向2个NN
汇报状态)。DN
接收到这个返回是认为该NN
为新的active
- 如果这时原来的
active
(比如GC)恢复,返回给DN
的心跳信息包含active
状态和原来的序列号,这时DN
就会拒绝这个NN
的命令。 - 在
failover
后,active
在DN
汇报所有删除报告前不应该删除任何block
。
看下来是不是和 Zookeeper
很相似,也是通过更大的 index
来决定谁才是 active
Client
确保只有一个 NN 能响应客户端请求。让访问 standby nn
的客户端直接失败。在 RPC
层封装了一层,通过 FailoverProxyProvider
以重试的方式连接 NN
。通过若干次连接一个 NN
失败后尝试连接新的 NN
,对客户端的影响是重试的时候增加一定的延迟。客户端可以设置重试此时和时间。
怎么能让 standby nn的客户端直接失败 ?
共享系统
正常情况下,active NN
把 editlog
日志写到共享系统,每个 editlog
有一个编号,每次写 editlog
只要其中大多数共享系统节点返回成功即认定写成功。
解决方案:NameNode
每次写 Editlog
都需要传递一个编号 Epoch
给共享系统,共享系统会对比 Epoch
,如果比自己保存的 Epoch
大或相同,则可以写,共享系统更新自己的 Epoch
到最新,否则拒绝操作。在切换时,Standby
转换为 Active
时,会把 Epoch+1
,这样就防止即使之前的 NameNode
向共享系统写日志,也会失败(详细看参考资料四)。
参考资料:
keepalived实现服务高可用
Zookeeper已经分布式环境中的假死脑裂
面试题:Zookeeper是如何解决脑裂问题
hadoop hdfs HA原理讲解、脑裂问题产生