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 10 - Cloud Replicated DB, Aurora

10.2 故障可恢复事务(Crash Recoverable Transaction)

上一页10.1 Aurora 背景历史下一页10.3 关系型数据库(Amazon RDS)

最后更新于4年前

这有帮助吗?

为了能更好的理解Aurora的设计,在进一步介绍它是如何工作之前,我们必须要知道典型的数据库是如何设计的。因为Aurora使用的是与MySQL类似的机制实现,但是又以一种有趣的方式实现了加速,所以我们需要知道一个典型的数据库是如何设计实现的,这样我们才能知道Aurora是如何实现加速的。

所以这一部分是数据库教程,但是实际上主要关注的是,如何实现一个故障可恢复事务(Crash Recoverable Transaction)。所以这一部分我们主要看的是事务(Transaction)和故障可恢复(Crash Recovery)。数据库还涉及到很多其他的方面,但是对于Aurora来说,这两部分最重要。

首先,什么是事务?事务是指将多个操作打包成原子操作,并确保多个操作顺序执行。假设我们运行一个银行系统,我们想在不同的银行账户之间转账。你可以这样看待一个事务,首先需要定义想要原子打包的多个操作的开始;之后是操作的内容,现在我们想要从账户Y转10块钱到账户X,那么账户X需要增加10块,账户Y需要减少10块;最后表明事务结束。

我们希望数据库顺序执行这两个操作,并且不允许其他任何人看到执行的中间状态。同时,考虑到故障,如果在执行的任何时候出现故障,我们需要确保故障恢复之后,要么所有操作都已经执行完成,要么一个操作也没有执行。这是我们想要从事务中获得的效果。除此之外,数据库的用户期望数据库可以通知事务的状态,也就是事务是否真的完成并提交了。如果一个事务提交了,用户期望事务的效果是可以持久保存的,即使数据库故障重启了,数据也还能保存。

通常来说,事务是通过对涉及到的每一份数据加锁来实现。所以你可以认为,在整个事务的过程中,都对X,Y加了锁。并且只有当事务结束、提交并且持久化存储之后,锁才会被释放。所以,数据库实际上在事务的过程中,是通过对数据加锁来确保其他人不能访问。这一点很重要,理解了这一点,论文中有一些细节才变得有意义。

所以,这里具体是怎么实现的呢?对于一个简单的数据库模型,数据库运行在单个服务器上,并且使用本地硬盘。

在硬盘上存储了数据的记录,或许是以B-Tree方式构建的索引。所以有一些data page用来存放数据库的数据,其中一个存放了X的记录,另一个存放了Y的记录。每一个data page通常会存储大量的记录,而X和Y的记录是page中的一些bit位。

在硬盘中,除了有数据之外,还有一个预写式日志(Write-Ahead Log,简称为WAL)。预写式日志对于系统的容错性至关重要。

在服务器内部,有数据库软件,通常数据库会对最近从磁盘读取的page有缓存。

当你在执行一个事务内的各个操作时,例如执行 X=X+10 的操作时,数据库会从硬盘中读取持有X的记录,给数据加10。但是在事务提交之前,数据的修改还只在本地的缓存中,并没有写入到硬盘。我们现在还不想向硬盘写入数据,因为这样可能会暴露一个不完整的事务。

为了让数据库在故障恢复之后,还能够提供同样的数据,在允许数据库软件修改硬盘中真实的data page之前,数据库软件需要先在WAL中添加Log条目来描述事务。所以在提交事务之前,数据库需要先在WAL中写入完整的Log条目,来描述所有有关数据库的修改,并且这些Log是写入磁盘的。

让我们假设,X的初始值是500,Y的初始值是750。

在提交并写入硬盘的data page之前,数据库通常需要写入至少3条Log记录:

  1. 第一条表明,作为事务的一部分,我要修改X,它的旧数据是500,我要将它改成510。

  2. 第二条表明,我要修改Y,它的旧数据是750,我要将它改成740。

  3. 第三条记录是一个Commit日志,表明事务的结束。

通常来说,前两条Log记录会打上事务的ID作为标签,这样在故障恢复的时候,可以根据第三条commit日志找到对应的Log记录,进而知道哪些操作是已提交事务的,哪些是未完成事务的。

学生提问:为什么在WAL的log中,需要带上旧的数据值?

Robert教授:在这个简单的数据库中,在WAL中只记录新的数据就可以了。如果出现故障,只需要重新应用所有新的数据即可。但是大部分真实的数据库同时也会在WAL中存储旧的数值,这样对于一个非常长的事务,只要WAL保持更新,在事务结束之前,数据库可以提前将更新了的page写入硬盘,比如说将Y写入新的数据740。之后如果在事务提交之前故障了,恢复的软件可以发现,事务并没有完成,所以需要撤回之前的操作,这时,这些旧的数据,例如Y的750,需要被用来撤回之前写入到data page中的操作。对于Aurora来说,实际上也使用了undo/redo日志,用来撤回未完成事务的操作。

如果数据库成功的将事务对应的操作和commit日志写入到磁盘中,数据库可以回复给客户端说,事务已经提交了。而这时,客户端也可以确认事务是永久可见的。

接下来有两种情况。

如果数据库没有崩溃,那么在它的cache中,X,Y对应的数值分别是510和740。最终数据库会将cache中的数值写入到磁盘对应的位置。所以数据库写磁盘是一个lazy操作,它会对更新进行累积,每一次写磁盘可能包含了很多个更新操作。这种累积更新可以提升操作的速度。

如果数据库在将cache中的数值写入到磁盘之前就崩溃了,这样磁盘中的page仍然是旧的数值。当数据库重启时,恢复软件会扫描WAL日志,发现对应事务的Log,并发现事务的commit记录,那么恢复软件会将新的数值写入到磁盘中。这被称为redo,它会重新执行事务中的写操作。

这就是事务型数据库的工作原理的简单描述,同时这也是一个极度精简的MySQL数据库工作方式的介绍,MySQL基本以这种方式实现了故障可恢复事务。而Aurora就是基于这个开源软件MYSQL构建的。