MIT6.824
  • 简介
  • Lecture 01 - Introduction
    • 1.1 分布式系统的驱动力和挑战(Drivens and Challenges)
    • 1.2 课程结构(Course Structure)
    • 1.3 分布式系统的抽象和实现工具(Abstraction and Implementation)
    • 1.4 可扩展性(Scalability)
    • 1.5 可用性(Availability)
    • 1.6 一致性(Consistency)
    • 1.7 MapReduce基本工作方式
    • 1.8 Map函数和Reduce函数
  • Lecture 03 - GFS
    • 3.1分布式存储系统的难点(Why Hard)
    • 3.2 错误的设计(Bad Design)
    • 3.3 GFS的设计目标
    • 3.4 GFS Master 节点
    • 3.5 GFS读文件(Read File)
    • 3.6 GFS写文件(Write File)(1)
    • 3.7 GFS写文件(Write File)(2)
    • 3.8 GFS的一致性
  • Lecture 04 - VMware FT
    • 4.1 复制(Replication)
    • 4.2 状态转移和复制状态机(State Transfer and Replicated State Machine)
    • 4.3 VMware FT 工作原理
    • 4.4 非确定性事件(Non-Deterministic Events)
    • 4.5 输出控制(Output Rule)
    • 4.6 重复输出(Duplicated Output)
    • 4.7 Test-and-Set 服务
  • Lecture 06 - Raft1
    • 6.1 脑裂(Split Brain)
    • 6.2 过半票决(Majority Vote)
    • 6.3 Raft 初探
    • 6.4 Log 同步时序
    • 6.5 日志(Raft Log)
    • 6.6 应用层接口
    • 6.7 Leader选举(Leader Election)
    • 6.8 选举定时器(Election Timer)
    • 6.9 可能的异常情况
  • Lecture 07 - Raft2
    • 7.1 日志恢复(Log Backup)
    • 7.2 选举约束(Election Restriction)
    • 7.3 快速恢复(Fast Backup)
    • 7.4 持久化(Persistence)
    • 7.5 日志快照(Log Snapshot)
    • 7.6 线性一致(Linearizability)
  • Lecture 08 - Zookeeper
    • 8.1 线性一致(Linearizability)(1)
    • 8.2 线性一致(Linearizability)(2)
    • 8.3 线性一致(Linearizability)(3)
    • 8.4 Zookeeper
    • 8.5 一致保证(Consistency Guarantees)
    • 8.6 同步操作(sync)
    • 8.7 就绪文件(Ready file/znode)
  • Lecture 09 - More Replication, CRAQ
    • 9.1 Zookeeper API
    • 9.2 使用Zookeeper实现计数器
    • 9.3 使用Zookeeper实现非扩展锁
    • 9.4 使用Zookeeper实现可扩展锁
    • 9.5 链复制(Chain Replication)
    • 9.6 链复制的故障恢复(Fail Recover)
    • 9.7 链复制的配置管理器(Configuration Manager)
  • Lecture 10 - Cloud Replicated DB, Aurora
    • 10.1 Aurora 背景历史
    • 10.2 故障可恢复事务(Crash Recoverable Transaction)
    • 10.3 关系型数据库(Amazon RDS)
    • 10.4 Aurora 初探
    • 10.5 Aurora存储服务器的容错目标(Fault-Tolerant Goals)
    • 10.6 Quorum 复制机制(Quorum Replication)
    • 10.7 Aurora读写存储服务器
    • 10.8 数据分片(Protection Group)
    • 10.9 只读数据库(Read-only Database)
  • Lecture 11 - Cache Consistency: Frangipani
    • 11.1 Frangipani 初探
    • 11.2 Frangipani的挑战(Challenges)
    • 11.3 Frangipani的锁服务(Lock Server)
    • 11.4 缓存一致性(Cache Coherence)
    • 11.5 原子性(Atomicity)
    • 11.6 Frangipani Log
    • 11.7 故障恢复(Crash Recovery)
    • 11.8 Frangipani总结
  • Lecture 12 - Distributed Transaction
    • 12.1 分布式事务初探(Distributed Transaction)
    • 12.2 并发控制(Concurrency Control)
    • 12.3 两阶段提交(Two-Phase Commit)
    • 12.4 故障恢复(Crash Recovery)
    • 12.5 总结
由 GitBook 提供支持
在本页

这有帮助吗?

  1. Lecture 06 - Raft1

6.1 脑裂(Split Brain)

上一页Lecture 06 - Raft1下一页6.2 过半票决(Majority Vote)

最后更新于4年前

这有帮助吗?

在之前的课程中,我们介绍了几个具备容错特性(fault-tolerant)的系统。如果你有留心的话,你会发现,它们有一个共同的特点。

  • MapReduce复制了计算,但是复制这个动作,或者说整个MapReduce被一个单主节点控制。

  • GFS以主备(primary-backup)的方式复制数据。它会实际的复制文件内容。但是它也依赖一个单主节点,来确定每一份数据的主拷贝的位置。

  • VMware FT,它在一个Primary虚机和一个Backup虚机之间复制计算相关的指令。但是,当其中一个虚机出现故障时,为了能够正确的恢复。需要一个Test-and-Set服务来确认,Primary虚机和Backup虚机只有一个能接管计算任务。

这三个例子中,它们都是一个多副本系统(replication system),但是在背后,它们存在一个共性:它们需要一个单节点来决定,在多个副本中,谁是主(Primary)。

使用一个单节点的好处是,它不可能否认自己。因为只有一个节点,它的决策就是整体的决策。但是使用单节点的缺点是,它本身又是一个单点故障(Single Point of Failure)。

所以,你可以认为我们前面介绍的这些系统,它们将系统容错的关键点,转移到了这个单点上。这个单点,会在系统出现局部故障时,选择数据的主拷贝来继续工作。使用单点的原因是,我们需要避免脑裂(Split-Brain)。当出现故障时,我们之所以要极其小心的决定数据的主拷贝,是因为,如果不这么做的话,我们可能需要面临脑裂的场景。

为了让同学们更深入的了解脑裂,我接下来会说明脑裂带来的问题,以及为什么这是个严重的问题。现在,假设我们将VMware FT中的Test-and-Set服务构建成多副本的。之前这是一个单点服务,而VMware FT依赖这个Test-and-Set服务来确定Primary虚机,所以,为了提高系统的容错性,我们来构建一个多副本的Test-and-Set服务。我们来看一下,为什么出现故障时,很难避免脑裂。

现在,我们来假设我们有一个网络,这个网络里面有两个服务器(S1,S2),这两个服务器都是我们Test-and-Set服务的拷贝。这个网络里面还有两个客户端(C1,C2),它们需要通过Test-and-Set服务确定主节点是谁。在这个例子中,这两个客户端本身就是VMware FT中的Primary和Backup虚拟机。

如果这是一个Test-and-Set服务,那么你知道这两个服务器中的数据记录将从0开始。任意一个客户端发送Test-and-Set指令,这个指令会将服务器中的状态设置成1。所以在这个图里面,两个服务器都应该设置成1,然后将旧的值0,返回给客户端。本质上来说,这是一种简化了的锁服务。

当一个客户端可以与其中一个服务器通信,但是不能与另一个通信时,有可能出现脑裂的问题。我们假设,客户端发送请求时,它会将请求同时发送给两个服务器。这样,我们就需要考虑,当某个服务器不响应时,客户端该怎么做?或者说,某个服务器不响应时,整个系统该如何响应?更具体点,我们假设C1可以访问S1但是不能访问S2,系统该如何响应?

一种情况是,我们必然不想让C1只与S1通信。因为,如果我们只将C1的请求设置给S1,而不设置给S2,会导致S2的数据不一致。所以,我们或许应该规定,对于任何操作,客户端必须总是与两个服务器交互,而不是只与其中一个服务器交互。但是这是一个错误的想法,为什么呢?因为这里根本就没有容错。这里甚至比只使用一个服务器更糟。因为当两个服务器中的一个故障了或者失联了,我们的系统就不能工作了。对于一个单点的服务,我们只依赖一个服务器。现在我们有两个服务器,并且两个服务器都必须一致在线,这里的难度比单个服务器更大。如果这种方式不是容错的,我们需要一种行之有效的方法。

另一个明显的答案是,如果客户端不能同时与两个服务器交互,那它就与它能连通的那个服务器交互,同时认为另一个服务器已经关机了。为什么这也是一个错误的答案呢?因为,我们的故障场景是,另一个服务器其实还开机着。我们假设我们经历的实际问题并不是这个服务器关机了,因为如果关机了对我们来说其实更好。实际情况可能更糟糕,实际可能是网络线路出现了故障,从而导致C1可以与S1交互,但是不能与S2交互。同时,C2可以与S2交互,但是不能与S1交互。现在我们规定,如果一个客户端连接了两个服务器,为了达到一定的容错性,客户端只与其中一个服务器交互也应该可以正常工作。但是这样就不可避免的出现了这种情况:假设这根线缆中断了,将网络分为两个部分。

C1发送Test-and-Set请求给S1,S1将自己的状态设置为1,并返回之前的状态0给C1。

这就意味着,C1会认为自己持有锁。如果这是一个VMware FT,C1对应的虚拟机会认为自己可以成为主节点。

但是同时,S2里面的状态仍然是0。所以如果现在C2也发送了一个Test-and-Set请求,本来应该发送给两个服务器,但是现在从C2看来,S1不能访问,根据之前定义的规则,那就发送给S2吧。同样的C2也会认为自己持有了锁。如果这个Test-and-Set服务被VMware FT使用,那么这两个VMware 虚机都会认为自己成为了主虚拟机而不需要与另一个虚拟机协商,所以这是一个错误的场景。

所以,在这种有两个拷贝副本的配置中,看起来我们只有两种选择:要么等待两个服务器响应,那么这个时候就没有容错能力;要么只等待一个服务器响应,那么就会进入错误的场景,而这种错误的场景,通常被称为脑裂。

这基本是上世纪80年代之前要面临的挑战。但是,当时又的确有多副本系统的要求。例如,控制电话交换机的计算机系统,或者是运行银行系统的计算机系统。当时的人们在构建多副本系统时,需要排除脑裂的可能。这里有两种技术:

  • 第一种是构建一个不可能出现故障的网络。实际上,不可能出现故障的网络一直在我们的身边。你们电脑中,连接了CPU和内存的线路就是不可能出现故障的网络。所以,带着合理的假设和大量的资金,同时小心的控制物理环境,比如不要将一根网线拖在地上,让谁都可能踩上去。如果网络不会出现故障,这样就排除了脑裂的可能。这里做了一些假设,但是如果有足够的资金,人们可以足够接近这个假设。当网络不出现故障时,那就意味着,如果客户端不能与一个服务器交互,那么这个服务器肯定是关机了。

  • 另一种就是人工解决问题,不要引入任何自动完成的操作。默认情况下,客户端总是要等待两个服务器响应,如果只有一个服务器响应,永远不要执行任何操作。相应的,给运维人员打电话,让运维人员去机房检查两个服务器。要么将一台服务器直接关机,要么确认一下其中一台服务器真的关机了,而另一个台还在工作。所以本质上,这里把人作为了一个决策器。而如果把人看成一台电脑的话,那么这个人他也是个单点。

所以,很长一段时间内,人们都使用以上两种方式中的一种来构建多副本系统。这虽然不太完美,因为人工响应不能很及时,而不出现故障的网络又很贵,但是这些方法至少是可行的。