分布式系统:Split-Brain

Posted by danner on March 5, 2018

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 NNwatch 临时节点,当 active NN 挂掉时临时节点被删除,standby NN 会收到临时节点删除的通知,此时切换成 active NN;以上就是 NN 的切换过程(HA)。 当 active NN 出现假死时,自然就会出现 Split-Brain。但不同于 Zookeeper 面对 slaveHadoop是面对 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 后,activeDN 汇报所有删除报告前不应该删除任何 block

看下来是不是和 Zookeeper 很相似,也是通过更大index 来决定谁才是 active

Client

确保只有一个 NN 能响应客户端请求。让访问 standby nn 的客户端直接失败。在 RPC层封装了一层,通过 FailoverProxyProvider 以重试的方式连接 NN。通过若干次连接一个 NN 失败后尝试连接新的 NN,对客户端的影响是重试的时候增加一定的延迟。客户端可以设置重试此时和时间。

怎么能让 standby nn的客户端直接失败 ?

共享系统

正常情况下,active NNeditlog 日志写到共享系统,每个 editlog 有一个编号,每次写 editlog 只要其中大多数共享系统节点返回成功即认定写成功。

解决方案:NameNode 每次写 Editlog 都需要传递一个编号 Epoch 给共享系统,共享系统会对比 Epoch,如果比自己保存的 Epoch 大或相同,则可以写,共享系统更新自己的 Epoch 到最新,否则拒绝操作。在切换时Standby 转换为 Active 时,会把 Epoch+1,这样就防止即使之前的 NameNode 向共享系统写日志,也会失败(详细看参考资料四)。

参考资料:

keepalived实现服务高可用
Zookeeper已经分布式环境中的假死脑裂
面试题:Zookeeper是如何解决脑裂问题
hadoop hdfs HA原理讲解、脑裂问题产生