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 11 - Cache Consistency: Frangipani

11.7 故障恢复(Crash Recovery)

上一页11.6 Frangipani Log下一页11.8 Frangipani总结

最后更新于4年前

这有帮助吗?

接下来,我们讨论一下,当工作站持有锁,并且故障了会发生什么。

这里的场景是,当工作站需要重命名文件或者创建一个文件时,首先它会获得所有需要修改数据的锁,之后修改自身的缓存来体现改动。但是后来工作站在向Petal写入数据的过程中故障了。工作站可能在很多个位置发生故障,但是由于前面介绍过的工作流程,Frangipani总是会先将自身的Log先写入到Petal。这意味着如果发生了故障,那么发生故障时可能会有这几种场景:

  • 要么工作站正在向Petal写入Log,所以这个时候工作站必然还没有向Petal写入任何文件或者目录。

  • 要么工作站正在向Petal写入修改的文件,所以这个时候工作站必然已经写入了完整的Log。

因为有了前面的工作流程,我们需要担心的故障发生时间点是有限的。

当持有锁的工作站崩溃了之后,发生的第一件事情是锁服务器向工作站发送一个Revoke消息,但是锁服务器得不到任何响应,之后才会触发故障恢复。如果没有人需要用到崩溃工作站持有的锁,那么基本上没有人会注意到工作站崩溃了。假设一个其他的工作站需要崩溃了的工作站所持有的一个锁,锁服务器会发出Revoke消息,但是锁服务器永远也不会从崩溃了的工作站收到Release消息。Frangipani出于一些原因对锁使用了租约,当租约到期了,锁服务器会认定工作站已经崩溃了,之后它会初始化恢复过程。实际上,锁服务器会通知另一个还活着的工作站说:看,工作站1看起来崩溃了,请读取它的Log,重新执行它最近的操作并确保这些操作完成了,在你完成之后通知我。在收到这里的通知之后,锁服务器才会释放锁。这就是为什么日志存放在Petal是至关重要的,因为一个其他的工作站可能会要读取这个工作站在Petal中的日志。

发生故障的场景究竟有哪些呢?

  • 第一种场景是,工作站WS1在向Petal写入任何信息之前就故障了。这意味着,当其他工作站WS2执行恢复,查看崩溃了的工作站的Log时,发现里面没有任何信息,自然也就不会做任何操作。之后WS2会释放WS1所持有的锁。工作站WS1或许在自己的缓存中修改了各种各样的数据,但是如果它没有在自己的Log存储区写入任何信息,那么它也不可能在Petal中写入任何它修改的块数据。我们会丢失WS1的最后几个操作,但是文件系统会与WS1开始修改之前保持一致。因为很明显,工作站WS1没能走到向Petal写Log那一步,自然也不可能向Petal写入块数据。

  • 第二种场景是,工作站WS1向Petal写了部分Log条目。这样的话,执行恢复的工作站WS2会从Log的最开始向后扫描,直到Log的序列号不再增加,因为这必然是Log结束的位置。工作站WS2会检查Log条目的更新内容,并向Petal执行Log条目中的更新内容。比如Petal中的特定块需要写入特定的数据,这里对应的其实就是工作站WS1在自己本地缓存中做的一些修改。所以执行恢复的工作站WS2会检查每个Log条目,并重新向Petal执行WS1的每一条Log。当WS2执行完WS1存放在Petal中的Log,它会通知锁服务器,之后锁服务器会释放WS1持有的锁。这样的过程会使得Petal更新至故障工作站WS1在故障前的执行的部分操作。或许不能全部恢复WS1的操作,因为故障工作站可能只向Petal写了部分Log就崩溃了。同时,除非在Petal中找到了完整的Log条目,否则执行恢复的工作站WS2是不会执行这条Log条目的,所以,这里的隐含意思是需要有类似校验和的机制,这样执行恢复的工作站就可以知道,这个Log条目是完整的,而不是只有操作的一部分数据。这一点很重要,因为在恢复时,必须要在Petal的Log存储区中找到完整的操作。所以,对于一个操作的所有步骤都需要打包在一个Log条目的数组里面,这样执行恢复的工作站就可以,要么全执行操作的所有步骤,要么不执行任何有关操作的步骤,但是永远不会只执行部分步骤。这就是当在向Petal写入Log时,发生了故障的修复过程。

  • 另一个有趣的可能是,工作站WS1在写入Log之后,并且在写入块数据的过程中崩溃了。先不考虑一些极其重要的细节,执行恢复的工作站WS2并不知道WS1在哪个位置崩溃的,它只能看到一些Log条目,同样的,WS2会以相同的方式重新执行Log。尽管部分修改已经写入了Petal,WS2会重新执行修改。对于部分已经写入的数据,相当于在相同的位置写入相同的数据。对于部分未写入的数据,相当于更新了Petal中的这部分数据,并完成了操作。

上面的描述并没有涵盖所有的场景,下面的这个场景会更加复杂一些。如果一个工作站,完成了上面流程的步骤1,2,在释放锁的过程中崩溃了,进而导致崩溃的工作站不是最后修改特定数据的工作站。具体可以看下面这个例子,假设我们有一个工作站WS1,它执行了删除文件(d/f)的操作。

之后,有另一个工作站WS2,在删除文件之后,以相同的名字创建了文件,当然这是一个不同的文件。所以之后,工作站WS2创建了同名的文件(d/f)。

在创建完成之后,工作站WS1崩溃了,

所以,我们需要基于WS1的Log执行恢复,这时,可能有第三个工作站WS3来执行恢复的过程。

这里的时序表明,WS1删除了一个文件,WS2创建了一个文件,WS3做了恢复操作。有可能删除操作仍然在WS1的Log中,当WS1崩溃后,WS3需要读取WS1的Log,并重新执行WS1的Log中的更新。因为删除文件的Log条目仍然存在于WS1的Log中,如果不做任何额外的事情,WS3会删除这个文件(d/f)。但是实际上,WS3删除的会是WS2稍后创建的一个完全不同的文件。

这样的结果是完全错误的,因为需要被删除的是WS1指定的文件,而不是WS2创建的一个相同名字的文件。因为WS2的创建是在WS1的删除之后,所以我们不能只是不经思考的重新执行WS1的Log,WS1的Log在我们执行的时候可能已经过时了,其他的一些工作站可能已经以其他的方式修改了相同的数据,所以我们不能盲目的重新执行Log条目。

Frangipani是这样解决这个问题的,通过对每一份存储在Petal文件系统数据增加一个版本号,同时将版本号与Log中描述的更新关联起来。在Petal中,每一个元数据,每一个inode,每一个目录下的内容,都有一个版本号,当工作站需要修改Petal中的元数据时,它会向从Petal中读取元数据,并查看当前的版本号,之后在创建Log条目来描述更新时,它会在Log条目中对应的版本号填入元数据已有的版本号加1。

之后,如果工作站执行到了写数据到Petal的步骤,它也会将新的增加了的版本号写回到Petal。

所以,如果一个工作站没有故障,并且成功的将数据写回到了Petal。这样元数据的版本号会大于等于Log条目中的版本号。如果有其他的工作站之后修改了同一份元数据,版本号会更高。

所以,实际上WS3看到的WS1的删除操作对应的Log条目,会有一个特定的版本号,它表明,由这个Log条目影响的元数据对应版本号3(举例)。

WS2的修改在WS1崩溃之前,所以WS1必然已经释放了相关数据的锁。WS2获得了锁,它会读取当前的元数据可以发现当前的版本号是3,当WS2写入数据的时候,它会将版本号设置为4。

之后,当WS3执行恢复流程时,WS3会重新执行WS1的Log,它会首先检查版本号,通过查看Log条目中的版本号,并查看Petal中存储的版本号,如果Petal中存储的版本号大于等于Log条目中的版本号,那么WS3会忽略Log条目中的修改,因为很明显Petal中的数据已经被故障了的工作站所更新,甚至可能被后续的其他工作站修改了。所以在恢复的过程中,WS3会选择性的根据版本号执行Log,只有Log中的版本号高于Petal中存储的数据的版本时,Log才会被执行。

这里有个比较烦人的问题就是,WS3在执行恢复,但是其他的工作站还在频繁的读取文件系统,持有了一些锁并且在向Petal写数据。WS3在执行恢复的过程中,WS2是完全不知道的。WS2可能还持有目录 d的锁,而WS3在扫描故障工作站WS1的Log时,需要读写目录d,但是目录d的锁还被WS2所持有。我们该如何解决这里的问题?

一种不可行的方法是,让执行恢复的WS3先获取所有关联数据的锁,再重新执行Log。这种方法不可行的一个原因是,有可能故障恢复是在一个大范围电力故障之后,这样的话谁持有了什么锁的信息都丢失了,因此我们也就没有办法使用之前的缓存一致性协议,因为哪些数据加锁了,哪些数据没有加锁在断电的过程中丢失了。

但是幸运的是,执行恢复的工作站可以直接从Petal读取数据而不用关心锁。这里的原因是,执行恢复的工作站想要重新执行Log条目,并且有可能修改与目录d关联的数据,它就是需要读取Petal中目前存放的目录数据。接下来只有两种可能,要么故障了的工作站WS1释放了锁,要么没有。如果没有的话,那么没有其他人不可以拥有目录的锁,执行恢复的工作站可以放心的读取目录数据,没有问题。如果释放了锁,那么在它释放锁之前,它必然将有关目录的数据写回到了Petal。这意味着,Petal中存储的版本号,至少会和故障工作站的Log条目中的版本号一样大,因此,之后恢复软件对比Log条目的版本号和Petal中存储的版本号,它就可以发现Log条目中的版本号并没有大于存储数据的版本号,那么这条Log条目就会被忽略。所以这种情况下,执行恢复的工作站可以不持有锁直接读取块数据,但是它最终不会更新数据。因为如果锁被释放了,那么Petal中存储的数据版本号会足够高,表明在工作站故障之前,Log条目已经应用到了Petal。所以这里不需要关心锁的问题。