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.9 可能的异常情况

上一页6.8 选举定时器(Election Timer)下一页Lecture 07 - Raft2

最后更新于4年前

这有帮助吗?

一个旧Leader在各种奇怪的场景下故障之后,为了恢复系统的一致性,一个新任的Leader如何能整理在不同副本上可能已经不一致的Log?

这个话题只在Leader故障之后才有意义,如果Leader正常运行,Raft不太会出现问题。如果Leader正在运行,并且在其运行时,系统中有过半服务器。Leader只需要告诉Followers,Log该是什么样子。Raft要求Followers必须同意并接收Leader的Log,这在Raft论文的图2中有说明。只要Followers还能处理,它们就会全盘接收Leader在AppendEntries中发送给它们的内容,并加到本地的Log中。之后再收到来自Leader的commit消息,在本地执行请求。这里很难出错。

在Raft中,当Leader故障了才有可能出错。例如,旧的Leader在发送消息的过程中故障了,或者新Leader在刚刚当选之后,还没来得及做任何操作就故障了。所以这里有一件事情我们非常感兴趣,那就是在一系列故障之后,Log会是怎样?

这里有个例子,假设我们有3个服务器(S1,S2,S3),我将写出每个服务器的Log,每一列对齐之后就是Log的一个槽位。我这里写的值是Log条目对应的任期号,而不是Log记录的客户端请求。所以第一列是槽位1,第二列是槽位2。所有节点在任期3的时候记录了一个请求在槽位1,S2和S3在任期3的时候记录了一个请求在槽位2。在槽位2,S1没有任何记录。

所以,这里的问题是:这种情况可能发生吗?如果可能发生,是怎么发生的?

这种情况是可能发生的。假设S3是任期3的Leader,它收到了一个客户端请求,之后发送给其他服务器。其他服务器收到了相应的AppendEntries消息,并添加Log到本地,这是槽位1的情况。之后,S3从客户端收到了第二个请求,它还是需要将这个请求发送给其他服务器。但是这里有三种情况:

  • 发送给S1的消息丢了

  • S1当时已经关机了

  • S3在向S2发送完AppendEntries之后,在向S1发送AppendEntries之前故障了

现在,只有S2和S3有槽位2的Log。Leader在发送AppendEntries消息之前,总是会将新的请求加到自己的Log中(所以S3有Log),而现在AppendEntries RPC只送到了S2(所以S2有Log)。这是不同节点之间Log不一样的一种最简单的场景。我们现在知道了它是如何发生的。

如果现任Leader S3故障了,首先我们需要新的选举,之后某个节点会被选为新的Leader。接下来会发生两件事情:

  • 新的Leader需要认识到,槽位2的请求可能已经commit了,从而不能丢弃。

  • 新的Leader需要确保S1在槽位2记录与其他节点完全一样的请求。

这里还有另外一个例子需要考虑。还是3个服务器,这次我会给Log的槽位加上数字,这样更方便我们后面说明。我们这里有槽位10、11、12、13。槽位10和槽位11类似于前一个例子。在槽位12,S2有一个任期4的请求,而S3有一个任期5的请求。在我们分析之前,我们需要明白,发生了什么会导致这个场景?我们需要清楚这个场景是否真的存在,因为有些场景不可能存在我们也就没必要考虑它。所以现在的问题是,这种场景可能发生吗?

这种场景是可能发生的。我们假设S2在槽位12时,是任期4的新Leader,它收到了来自客户端的请求,将这个请求加到了自己的Log中,然后就故障了。

因为Leader故障了,我们需要一次新的选举。我们来看哪个服务器可以被选为新的Leader。这里S3可能被选上,因为它只需要从过半服务器获得认可投票,而在这个场景下,过半服务器就是S1和S3。所以S3可能被选为任期5的新Leader,之后收到了来自客户端的请求,将这个请求加到自己的Log中,然后故障了。之后就到了例子中的场景了。

因为可能发生,Raft必须能够处理这种场景。在我们讨论Raft会如何做之前,我们必须了解,怎样才是一种可接受的结果。大概看一眼这个图,我们知道在槽位10的Log,3个副本都有记录,它可能已经commit了,所以我们不能丢弃它。类似的在槽位11的Log,因为它被过半服务器记录了,它也可能commit了,所以我们也不能丢弃它。在槽位12记录的两个Log(分别是任期4和任期5),都没有被commit,所以Raft可以丢弃它们。这里没有要求必须都丢弃它们,但是至少需要丢弃一个Log,因为最终你还是要保持多个副本之间的Log一致。

学生提问:槽位10和11的请求必然执行成功了吗?

Robert教授:对于槽位11,甚至对于槽位10,我们不能从Log中看出来Leader在故障之前到底执行到了哪一步。有一种可能是Leader在发送完AppendEntries之后就立刻故障了,所以Leader没能收到其他副本的确认,相应的请求也就不会commit,进而也就不会执行这个请求,所以它也就不会发出增加了的commit值,其他副本也就可能也没有执行这个请求。所以完全可能槽位10和槽位11的请求没有被执行。如果Raft能知道这些,那么丢弃槽位10和槽位11的Log也是合法的,因为它们没有被commit。但是从Log上看,没有办法否认这些请求被commit了。换句话说,这些请求可能commit了。所以Raft必须认为它们已经被commit了,因为完全有可能,Leader是在对这些请求走完完整流程之后再故障。所以这里,我们不能排除Leader已经返回响应给客户端的可能性,只要这种可能性存在,我们就不能将槽位10和槽位11的Log丢弃,因为客户端可能已经知道了这个请求被执行了。所以我们必须假设这些请求被commit了。

我们会在下一节课继续这个话题。