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.5 日志快照(Log Snapshot)

上一页7.4 持久化(Persistence)下一页7.6 线性一致(Linearizability)

最后更新于4年前

这有帮助吗?

Log压缩和快照(Log compaction and snapshots)在Lab3b中出现的较多。在Raft中,Log压缩和快照解决的问题是:对于一个长期运行的系统,例如运行了几周,几个月甚至几年,如果我们按照Raft论文图2的规则,那么Log会持续增长。最后可能会有数百万条Log,从而需要大量的内存来存储。如果持久化存储在磁盘上,最终会消耗磁盘的大量空间。如果一个服务器重启了,它需要通过重新从头开始执行这数百万条Log来重建自己的状态。当故障重启之后,遍历并执行整个Log的内容可能要花费几个小时来完成。这在某种程度上来说是浪费,因为在重启之前,服务器已经有了一定的应用程序状态。

为了应对这种场景,Raft有了快照(Snapshots)的概念。快照背后的思想是,要求应用程序将其状态的拷贝作为一种特殊的Log条目存储下来。我们之前几乎都忽略了应用程序,但是事实是,假设我们基于Raft构建一个key-value数据库,Log将会包含一系列的Put/Get或者Read/Write请求。假设一条Log包含了一个Put请求,客户端想要将X设置成1,另一条Log想要将X设置成2,下一条将Y设置成7。

如果Raft一直执行没有故障,Raft之上的将会是应用程序,在这里,应用程序将会是key-value数据库。它将会维护一个表单,当Raft一个接一个的上传命令时,应用程序会更新它的表单。

所以第一个命令之后,应用程序会将表单中的X设置为1。

第二个命令之后,表单中的X会被设置为2。

第三个命令之后,表单中的Y会被设置为7。

这里有个有趣的事实,那就是:对于大多数的应用程序来说,应用程序的状态远小于Log的大小。某种程度上我们知道,在某些时间点,Log和应用程序的状态是可以互换的,它们是用来表示应用程序状态的不同事物。但是Log可能包含大量的重复的记录(例如对于X的重复赋值),这些记录使用了Log中的大量的空间,但是同时却压缩到了key-value表单中的一条记录。这在多副本系统中很常见。在这里,如果存储Log,可能尺寸会非常大,相应的,如果存储key-value表单,这可能比Log尺寸小得多。这就是快照的背后原理。

所以,当Raft认为它的Log将会过于庞大,例如大于1MB,10MB或者任意的限制,Raft会要求应用程序在Log的特定位置,对其状态做一个快照。所以,如果Raft要求应用程序做一个快照,Raft会从Log中选取一个与快照对应的点,然后要求应用程序在那个点的位置做一个快照。这里极其重要,因为我们接下来将会丢弃所有那个点之前的Log记录。如果我们有一个点的快照,那么我们可以安全的将那个点之前的Log丢弃。(在key-value数据库的例子中)快照本质上就是key-value表单。

我们还需要为快照标注Log的槽位号。在这个图里面,这个快照对应的正好是槽位3。

有了快照,并且Raft将它存放在磁盘中之后,Raft将不会再需要这部分Log。只要Raft持久化存储了快照,快照对应的Log槽位号,以及Log槽位号之后的所有Log,那么快照对应槽位号之前的这部分Log可以被丢弃,我们将不再需要这部分Log。

所以这就是Raft快照的工作原理,Raft要求应用程序做快照,得到快照之后将其存储在磁盘中,同时持久化存储快照之后的Log,并丢弃快照之前的Log。所以,Raft的持久化存储实际上是持久化应用程序快照,和快照之后的Log。大家都明白了吗?

学生提问:听不清。

Robert教授:或许可以这样看这些Log,快照之后的Log是实际存在的,而快照之前的Log可以认为是幽灵条目,我们可以认为它们还在那,只是说我们永远不会再去查看它们了, 因为我们现在有快照了。事实上,我们不再存储幽灵条目,但是效果上是等效于有完整的Log。

刚刚的回答可能有些草率。因为如果按照Raft论文的图2,你有时还是需要这些早期的Log(槽位1,2,3)。所以,在知道了有时候某些Log可能不存在的事实之后,你可能需要稍微重新理解一下图2。

所以,重启的时候会发生什么呢?现在,重启的场景比之前只有Log会更加复杂一点。重启的时候,必须让Raft有方法知道磁盘中最近的快照和Log的组合,并将快照传递给应用程序。因为现在我们不能重演所有的Log(部分被删掉了),所以必须要有一种方式来初始化应用程序。所以应用程序不仅需要有能力能生成一个快照,它还需要能够吸纳一个之前创建的快照,并通过它稳定的重建自己的内存。所以,尽管Raft在管理快照,快照的内容实际上是应用程序的属性。Raft并不理解快照中有什么,只有应用程序知道,因为快照里面都是应用程序相关的信息。所以重启之后,应用程序需要能够吸纳Raft能找到的最近的一次快照。到目前为止还算简单。

不幸的是,这里丢弃了快照之前的Log,引入了大量的复杂性。如果有的Follower的Log较短,在Leader的快照之前就结束,那么除非有一种新的机制,否则那个Follower永远也不可能恢复完整的Log。因为,如果一个Follower只有前两个槽位的Log,Leader不再有槽位3的Log可以通过AppendEntries RPC发给Follower,Follower的Log也就不可能补齐至Leader的Log。

我们可以通过这种方式来避免这个问题:如果Leader发现有任何一个Follower的Log落后于Leader要做快照的点,那么Leader就不丢弃快照之前的Log。Leader原则上是可以知道Follower的Log位置,然后Leader可以不丢弃所有Follower中最短Log之后的本地Log。

这或许是一个短暂的好方法,之所以这个方法不完美的原因在于,如果一个Follower关机了一周,它也就不能确认Log条目,同时也意味着Leader不能通过快照来减少自己的内存消耗(因为那个Follower的Log长度一直没有更新)。

所以,Raft选择的方法是,Leader可以丢弃Follower需要的Log。所以,我们需要某种机制让AppendEntries能处理某些Follower Log的结尾到Leader Log开始之间丢失的这一段Log。解决方法是(一个新的消息类型)InstallSnapshot RPC。

当Follower刚刚恢复,如果它的Log短于Leader通过 AppendEntries RPC发给它的内容,那么它首先会强制Leader回退自己的Log。在某个点,Leader将不能再回退,因为它已经到了自己Log的起点。这时,Leader会将自己的快照发给Follower,之后立即通过AppendEntries将后面的Log发给Follower。

不幸的是,这里明显的增加了的复杂度。因为这里需要Raft组件之间的协同,这里还有点违反模块性,因为这里需要组件之间有一些特殊的协商。例如,当Follower收到了InstallSnapshot,这个消息是被Raft收到的,但是Raft实际需要应用程序能吸纳这个快照。所以它们现在需要更多的交互了。

学生提问:快照的创建是否依赖应用程序?

Robert教授:肯定依赖。快照生成函数是应用程序的一部分,如果是一个key-value数据库,那么快照生成就是这个数据库的一部分。Raft会通过某种方式调用到应用程序,通知应用程序生成快照,因为只有应用程序自己才知道自己的状态(进而能生成快照)。而通过快照反向生成应用程序状态的函数,同样也是依赖应用程序的。但是这里又有点纠缠不清,因为每个快照又必须与某个Log槽位号对应。

学生提问:如果RPC消息乱序该怎么处理?

Robert教授:是在说Raft论文图13的规则6吗?这里的问题是,你们会在Lab3遇到这个问题,因为RPC系统不是完全的可靠和有序,RPC可以乱序的到达,甚至不到达。你或许发了一个RPC,但是收不到回复,并认为这个消息丢失了,但是消息实际上送达了,实际上是回复丢失了。所有这些都可能发生,包括发生在InstallSnapshot RPC中。Leader几乎肯定会并发发出大量RPC,其中包含了AppendEntries和InstallSnapshot,因此,Follower有可能受到一条很久以前的InstallSnapshot消息。因此,Follower必须要小心应对InstallSnapshot消息。我认为,你想知道的是,如果Follower收到了一条InstallSnapshot消息,但是这条消息看起来完全是冗余的,这条InstallSnapshot消息包含的信息比当前Follower的信息还要老,这时,Follower该如何做?

Raft论文图13的规则6有相应的说明。我认为正常的响应是,Follower可以忽略明显旧的快照。其实我(Robert教授)看不懂那条规则6。