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 07 - Raft2

7.1 日志恢复(Log Backup)

上一页Lecture 07 - Raft2下一页7.2 选举约束(Election Restriction)

最后更新于4年前

这有帮助吗?

(接6.9 的内容)

我们现在处于这样一个场景

我们假设下一个任期是6。尽管你无法从黑板上确认这一点,但是下一个任期号至少是6或者更大。我们同时假设S3在任期6被选为Leader。在某个时刻,新Leader S3会发送任期6的第一个AppendEntries RPC,来传输任期6的第一个Log,这个Log应该在槽位13。

这里的AppendEntries消息实际上有两条,因为要发给两个Followers。它们包含了客户端发送给Leader的请求。我们现在想将这个请求复制到所有的Followers上。这里的AppendEntries RPC还包含了prevLogIndex字段和prevLogTerm字段。所以Leader在发送AppendEntries消息时,会附带前一个槽位的信息。在我们的场景中,prevLogIndex是前一个槽位的位置,也就是12;prevLogTerm是S3上前一个槽位的任期号,也就是5。

这样的AppendEntries消息发送给了Followers。而Followers,它们在收到AppendEntries消息时,可以知道它们收到了一个带有若干Log条目的消息,并且是从槽位13开始。Followers在写入Log之前,会检查本地的前一个Log条目,是否与Leader发来的有关前一条Log的信息匹配。

所以对于S2 它显然是不匹配的。S2 在槽位12已经有一个条目,但是它来自任期4,而不是任期5。所以S2将拒绝这个AppendEntries,并返回False给Leader。S1在槽位12还没有任何Log,所以S1也将拒绝Leader的这个AppendEntries。到目前位置,一切都还好。为什么这么说呢?因为我们完全不想看到的是,S2 把这条新的Log添加在槽位13。因为这样会破坏Raft论文中图2所依赖的归纳特性,并且隐藏S2 实际上在槽位12有一条不同的Log的这一事实。

所以S1和S2都没有接受这条AppendEntries消息,所以,Leader看到了两个拒绝。

Leader为每个Follower维护了nextIndex。所以它有一个S2的nextIndex,还有一个S1的nextIndex。之前没有说明的是,如果Leader之前发送的是有关槽位13的Log,这意味着Leader对于其他两个服务器的nextIndex都是13。这种情况发生在Leader刚刚当选,因为Raft论文的图2规定了,nextIndex的初始值是从新任Leader的最后一条日志开始,而在我们的场景中,对应的就是槽位13.

为了响应Followers返回的拒绝,Leader会减小对应的nextIndex。所以它现在减小了两个Followers的nextIndex。这一次,Leader发送的AppendEntries消息中,prevLogIndex等于11,prevLogTerm等于3。同时,这次Leader发送的AppendEntries消息包含了prevLogIndex之后的所有条目,也就是S3上槽位12和槽位13的Log。

对于S2来说,这次收到的AppendEntries消息中,prevLogIndex等于11,prevLogTerm等于3,与自己本地的Log匹配,所以,S2会接受这个消息。Raft论文中的图2规定,如果接受一个AppendEntries消息,那么需要首先删除本地相应的Log(如果有的话),再用AppendEntries中的内容替代本地Log。所以,S2会这么做:它会删除本地槽位12的记录,再添加AppendEntries中的Log条目。这个时候,S2的Log与S3保持了一致。

但是,S1仍然有问题,因为它的槽位11是空的,所以它不能匹配这次的AppendEntries。它将再次返回False。而Leader会将S1对应的nextIndex变为11,并在AppendEntries消息中带上从槽位11开始之后的Log(也就是槽位11,12,13对应的Log)。并且带上相应的prevLogIndex(10)和prevLogTerm(3)。

这次的请求可以被S1接受,并得到肯定的返回。现在它们都有了一致的Log。

而Leader在收到了Followers对于AppendEntries的肯定的返回之后,它会增加相应的nextIndex到14。

在这里,Leader使用了一种备份机制来探测Followers的Log中,第一个与Leader的Log相同的位置。在获得位置之后,Leader会给Follower发送从这个位置开始的,剩余的全部Log。经过这个过程,所有节点的Log都可以和Leader保持一致。

重复一个我们之前讨论过的话题,或许我们还会再讨论。在刚刚的过程中,我们擦除了一些Log条目,比如我们刚刚删除了S2中的槽位12的Log。这个位置是任期4的Log。现在的问题是,为什么Raft系统可以安全的删除这条记录?毕竟我们在删除这条记录时,某个相关的客户端请求也随之被丢弃了。

我在上堂课说过这个问题,这里的原理是什么呢?是的,这条Log条目并没有存在于过半服务器中,因此无论之前的Leader是谁,发送了这条Log,它都没有得到过半服务器的认可。因此旧的Leader不可能commit了这条记录,也就不可能将它应用到应用程序的状态中,进而也就不可能回复给客户端说请求成功了。因为它没有存在于过半服务器中,发送这个请求的客户端没有理由认为这个请求被执行了,也不可能得到一个回复。因为这里有一条规则就是,Leader只会在commit之后回复给客户端。客户端甚至都没有理由相信这个请求被任意服务器收到了。并且,Raft论文中的图2说明,如果客户端发送请求之后一段时间没有收到回复,它应该重新发送请求。所以我们知道,不论这个被丢弃的请求是什么,我们都没有执行它,没有把它包含在任何状态中,并且客户端之后会重新发送这个请求。

学生提问:前面的过程中,为什么总是删除Followers的Log的结尾部分?

Robert教授:一个备选的答案是,Leader有完整的Log,所以当Leader收到有关AppendEntries的False返回时,它可以发送完整的日志给Follower。如果你刚刚启动系统,甚至在一开始就发生了非常反常的事情,某个Follower可能会从第一条Log 条目开始恢复,然后让Leader发送整个Log记录,因为Leader有这些记录。如果有必要的话,Leader拥有填充每个节点的日志所需的所有信息。

我们不想看到的场景