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。

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

如果之后数据库服务器将自身缓存的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条目。

所以故障之后,新的数据库服务器需要恢复,它会执行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的事务。

最后更新于