Loading

文件系统崩溃不一致问题

问题:数据不一致

假设现在我们有一个普通的文件系统,它由简单的inode块、数据块和它们各自的位图组成。

img

现在有一个inode——II[v1]代表它目前是第一个版本,它的一个指针指向了数据块Da

版本只是为了举例子,并不是文件系统记录了inode的版本信息

现在,你需要打开这个文件,跳到它的末尾(使用lseek),然后追加写入一个新块Db,你要对如下块进行读写:

  1. 数据块位图
  2. 新的数据块Db
  3. I[v1]中添加指向新块的指针,变成I[v2]

这三次写入不是原子的,如果在三次写入之间崩溃,可能造成多种数据不一致问题:

  1. 只有Db写入成功:不会造成不一致,因为没有inode引用它,数据块位图中也认为它是没有被使用的
  2. 只有I[v2]写入成功:不一致,位图中表示没有人使用Db数据块,但I[v2]却指向了它,并且Db块中目前的数据是未知的
  3. 只有数据块位图写入成功:不一致,位图中表示Db已经被使用,但系统中却没有inode引用它

除此之外,还可能出现三次写入中的两次成功的情况,实际的磁盘写入可能会复杂很多,上面举这么多例子只是为了说明崩溃时可能产生数据不一致的问题

预写日志

这里竟然有好多数据库、JVM、MQ等各种各样的系统中见到的各种技术,果然这东西都是相通的。

思路概述

预写日志的思路就是在对磁盘进行实际写入之前,先将一些辅助信息写入到日志中,这些辅助信息可以是任何形式,甚至可以完完全全是待写入信息,只要在发生崩溃时能够通过它将磁盘恢复到一致状态即可。当日志写入后,再进行实际的写入操作。会出现两种情况:

  1. 写日志时崩溃:磁盘中的实际数据不可能不一致,只是日志数据可能不一致
  2. 写实际数据时崩溃:可以通过日志数据恢复一致性

虽然日志数据也存在磁盘里,但是它并不是用户所产生的数据,只是系统为了保证一致性所产生的辅助数据,所以我们将它们分开想

如下第一张图是ext2文件系统的一个简单示意图,其中有一个super块,磁盘的其它位置被分成若干块组,块组内部有自己的inode区和数据区以及位图。第二张图是ext3文件系统的一个示意图,其多的部分就是多了一个日志块区(Journal),它用于保存系统中的日志

img
img

该思想的相关应用:InnoDB redolog、两次写缓冲

数据日志示例

img

一条日志以TxB开始(事务Begin),中间可以保存任意格式的日志数据,以TxE结束(事务End)。在上面的示例中,TxBTxE之间保存了inode块I、数据块位图块B以及数据块Db,也就是它把我们一会儿要写的所有东西都原封不动的写到日志里了,这会带来双倍的磁盘带宽占用,稍后会看到更优雅的办法。

有了数据日志,执行磁盘更新操作就可以分为两步:

  1. 日志写入
  2. 加检查点:检查点就是一个要将待写入数据实际写入磁盘的时间点,一旦检查点产生,该检查点之前的所有日志所关联的待写入信息已经安全落盘,相关日志无效

同时注意,日志的写入也不是原子的,有可能出现其它块都写入只有Db块还没写入就崩溃的情况,此时如果按照它来恢复系统,那么Db数据块的内容将是不确定的。这可以通过在日志数据全部写入完成后最后写入TxE来规避。

磁盘能保证512KB的原子写入,所以TxE最好是512KB,我记得InnoDB里有个地方可以不用走两次写缓冲,也是因为它的写入是以512KB为单位。

该思想的相关应用:InnoDB/Kafka checkpoint技术,还有个不确定,Kafka的事务日志好像有点类似,但是那个学了一丢丢就弃坑了

环形日志

我们不能让日志占用磁盘的任意大小空间,毕竟磁盘主要是用来存储用户数据的,这代表我们必须限制日志可以使用的空间大小,当到达指定大小,就不能在处理磁盘修改操作。

一旦事务被加检查点,文件系统要在其后的某个时间顺序地释放它们的空间,供未来使用,而日志块组可以被视作一个环形结构,日志块组中的超级日志块中可以保存系统中目前可用(实际写入未落盘)的最早日志和最晚日志,而其它空间都是空闲的。

现在,我们可以重新梳理一下整个过程:

  1. 日志写入
  2. 日志提交:写入TxE
  3. 加检查点
  4. 日志释放

该思想的相关应用:InnoDB环形日志、redis的环形repl_log

批处理日志更新

想象你要在一个目录中创建两个文件,对于每一个文件,你都要修改一次inode位图、数据块位图以及父目录的数据块。

一些文件系统可以将所有更新缓冲到全局事务中,然后批量提交。

该思想的相关应用:InnoDB Insert Buffer、Kafka batch produce和batch consume

更小的日志:元数据日志

上面的策略确实可以获得很好的一致性以及崩溃后极快的恢复速度,ext3中在使用这种日志,但是它的日志太大了,它把所有数据都缓冲了,所以也称它“数据日志”。

另一种日志称作元数据日志或有序日志,它只在日志中保存元数据,而不保存实际写入的数据,元数据的定义包括:

  1. inode块
  2. 各种位图
  3. 目录的数据块

和文件数据块相比,元数据通常比较小。

同时,为了保证不会出现数据不一致,必须保证日志中没有记录的数据(即文件数据块)已经被正确写入磁盘后再写入其它日志数据:

  1. 数据写入
  2. 日志元数据写入
  3. 日志提交
  4. 加检查点
  5. 释放

如果在第一步就崩溃,这些数据相当于没写入过,并没有inode实际指向这些数据块,并且位图中它们也是空闲的

并非2345步要等待第1步完成,实际上只要在3之前完成1和2即可

删除文件的简单介绍

删除一个文件后它的块应该能被复用,这会出现一些问题,具体懒得写了,解决办法有俩:

  1. 直到该块加检查点后才复用该块
  2. 日志中添加新的记录类型,比如撤销操作(即让删除操作可被重放)

其它方法

posted @ 2022-11-08 15:26  yudoge  阅读(101)  评论(0编辑  收藏  举报