> For the complete documentation index, see [llms.txt](https://mit-public-courses-cn-translatio.gitbook.io/mit6-824/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mit-public-courses-cn-translatio.gitbook.io/mit6-824/lecture-10-cloud-replicated-db-aurora/10.7.md).

# 10.7 Aurora读写存储服务器

我之前也解释过，Aurora中的写请求并不是像一个经典的Quorum系统一样直接更新数据。对于Aurora来说，它的写请求从来不会覆盖任何数据，它的写请求只会在当前Log中追加条目（Append Entries）。所以，Aurora使用Quorum只是在数据库执行事务并发出新的Log记录时，确保Log记录至少出现在4个存储服务器上，之后才能提交事务。所以，Aurora的Write Quorum的实际意义是，每个新的Log记录必须至少追加在4个存储服务器中，之后才可以认为写请求完成了。当Aurora执行到事务的结束，并且在回复给客户端说事务已经提交之前，Aurora必须等待Write Quorum的确认，也就是4个存储服务器的确认，组成事务的每一条Log都成功写入了。

实际上，在一个故障恢复过程中，事务只能在之前所有的事务恢复了之后才能被恢复。所以，实际中，在Aurora确认一个事务之前，它必须等待Write Quorum确认之前所有已提交的事务，之后再确认当前的事务，最后才能回复给客户端。

这里的存储服务器接收Log条目，这是它们看到的写请求。它们并没有从数据库服务器获得到新的data page，它们得到的只是用来描述data page更新的Log条目。

但是存储服务器内存最终存储的还是数据库服务器磁盘中的page。在存储服务器的内存中，会有自身磁盘中page的cache，例如page1（P1），page2（P2），这些page其实就是数据库服务器对应磁盘的page。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MFhb6-b_vrI0XljgMGs%2F-MFi0Sr5zoTjEE8VMIOy%2Fimage.png?alt=media\&token=b8a373ba-1d65-4a20-8d52-e581ca4fc41a)

当一个新的写请求到达时，这个写请求只是一个Log条目，Log条目中的内容需要应用到相关的page中。但是我们不必立即执行这个更新，可以等到数据库服务器或者恢复软件想要查看那个page时才执行。对于每一个存储服务器存储的page，如果它最近被一个Log条目修改过，那么存储服务器会在内存中缓存一个旧版本的page和一系列来自于数据库服务器有关修改这个page的Log条目。所以，对于一个新的Log条目，它会立即被追加到影响到的page的Log列表中。这里的Log列表从上次page更新过之后开始（相当于page是snapshot，snapshot后面再有一系列记录更新的Log）。如果没有其他事情发生，那么存储服务器会缓存旧的page和对应的一系列Log条目。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MFhb6-b_vrI0XljgMGs%2F-MFi2yQAmOdZACNYySVe%2Fimage.png?alt=media\&token=829468c8-4f3d-4e5b-927a-fe6c5a55793b)

如果之后数据库服务器将自身缓存的page删除了，过了一会又需要为一个新的事务读取这个page，它会发出一个读请求。请求发送到存储服务器，会要求存储服务器返回当前最新的page数据。在这个时候，存储服务器才会将Log条目中的新数据更新到page，并将page写入到自己的磁盘中，之后再将更新了的page返回给数据库服务器。同时，存储服务器在自身cache中会删除page对应的Log列表，并更新cache中的page，虽然实际上可能会复杂的多。

如刚刚提到的，数据库服务器有时需要读取page。所以，可能你已经发现了，数据库服务器写入的是Log条目，但是读取的是page。这也是与Quorum系统不一样的地方。Quorum系统通常读写的数据都是相同的。除此之外，在一个普通的操作中，数据库服务器可以避免触发Quorum Read。数据库服务器会记录每一个存储服务器接收了多少Log。所以，首先，Log条目都有类似12345这样的编号，当数据库服务器发送一条新的Log条目给所有的存储服务器，存储服务器接收到它们会返回说，我收到了第79号和之前所有的Log。数据库服务器会记录这里的数字，或者说记录每个存储服务器收到的最高连续的Log条目号。这样的话，当一个数据库服务器需要执行读操作，它只会挑选拥有最新Log的存储服务器，然后只向那个服务器发送读取page的请求。所以，数据库服务器执行了Quorum Write，但是却没有执行Quorum Read。因为它知道哪些存储服务器有最新的数据，然后可以直接从其中一个读取数据。这样的代价小得多，因为这里只读了一个副本，而不用读取Quorum数量的副本。

但是，数据库服务器有时也会使用Quorum Read。假设数据库服务器运行在某个EC2实例，如果相应的硬件故障了，数据库服务器也会随之崩溃。在Amazon的基础设施有一些监控系统可以检测到Aurora数据库服务器崩溃，之后Amazon会自动的启动一个EC2实例，在这个实例上启动数据库软件，并告诉新启动的数据库：你的数据存放在那6个存储服务器中，请清除存储在这些副本中的任何未完成的事务，之后再继续工作。这时，Aurora会使用Quorum的逻辑来执行读请求。因为之前数据库服务器故障的时候，它极有可能处于执行某些事务的中间过程。所以当它故障了，它的状态极有可能是它完成并提交了一些事务，并且相应的Log条目存放于Quorum系统。同时，它还在执行某些其他事务的过程中，这些事务也有一部分Log条目存放在Quorum系统中，但是因为数据库服务器在执行这些事务的过程中崩溃了，这些事务永远也不可能完成。对于这些未完成的事务，我们可能会有这样一种场景，第一个副本有第101个Log条目，第二个副本有第102个Log条目，第三个副本有第104个Log条目，但是没有一个副本持有第103个Log条目。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MFm4umOtKBcv00JRuuU%2F-MFmKA8w4ri_7IDIfepj%2Fimage.png?alt=media\&token=4efe4b95-9dcd-412f-95df-769206ff3e37)

所以故障之后，新的数据库服务器需要恢复，它会执行Quorum Read，找到第一个缺失的Log序号，在上面的例子中是103，并说，好吧，我们现在缺失了一个Log条目，我们不能执行这条Log之后的所有Log，因为我们缺失了一个Log对应的更新。

所以，这种场景下，数据库服务器执行了Quorum Read，从可以连接到的存储服务器中发现103是第一个缺失的Log条目。这时，数据库服务器会给所有的存储服务器发送消息说：请丢弃103及之后的所有Log条目。103及之后的Log条目必然不会包含已提交的事务，因为我们知道只有当一个事务的所有Log条目存在于Write Quorum时，这个事务才会被commit，所以对于已经commit的事务我们肯定可以看到相应的Log。这里我们只会丢弃未commit事务对应的Log条目。

所以，某种程度上，我们将Log在102位置做了切割，102及之前的Log会保留。但是这些会保留的Log中，可能也包含了未commit事务的Log，数据库服务器需要识别这些Log。这是可行的，可以通过Log条目中的事务ID和事务的commit Log条目来判断（10.3）哪些Log属于已经commit的事务，哪些属于未commit的事务。数据库服务器可以发现这些未完成的事务对应Log，并发送undo操作来撤回所有未commit事务做出的变更。这就是为什么Aurora在Log中同时也会记录旧的数值的原因。因为只有这样，数据库服务器在故障恢复的过程中，才可以回退之前只提交了一部分，但是没commit的事务。
