🐧 第七章 文件系统
📄 一句话概括

三种日志模式:writeback 最快(1.0 倍)但可能读到旧数据;ordered 多花 30% 挡住这个坑;journal 要多花一倍多、全都保。发行版默认 ordered——用 30% 换掉最恶心的那个坑。

📝 本节和前面几节的关系

7.5 讲了日志的原理,这一节讲它的三种配置,以及配置之外的最后一道防线:fsck。选哪个不是【越安全越好】,是【谁该负责一致性】的问题。

一、三种模式,差在【数据进不进日志】

writeback:元数据走日志,数据直接写。最快,但有个恶心的坑(下面细说)。 ordered:元数据走日志,数据【先落盘】,然后才允许提交元数据。多一次等待,但不会读到旧数据。 journal:数据和元数据都进日志。最安全,写入量最大。 三者的元数据都受日志保护,差别只在【数据】这一半。 所以它们都解决 7.4 的【元数据不一致】,只是在对【数据一致性】上取了不同的折中。

二、writeback 那个坑

writeback 的问题在于【元数据可能比数据先到盘上】。 元数据说【文件已经变成 8192 字节了】,而新写的那 4 KB 还在缓存里。这时断电,文件长度对,后半截内容是【上一次被别人用过的旧数据】。 这不只是【丢数据】,是【泄露别人的数据】——一个安全性问题,不只是可靠性问题。 ordered 的做法很聪明:不要求数据进日志,只要求【数据先落盘】。代价是一次额外的等待,实测大约多 30%。换掉的是上面那个泄露。 这就是为什么几乎所有发行版默认 ordered:它用 30% 的代价拿掉了最恶心的那个坑。

三、那为什么还有人用 journal

因为 ordered 有一处管不到:追加写。 往文件末尾追加时,如果新数据块里混着【上一次被删文件的残留】,文件长度会先变长,泄露几个字节的旧内容。这个坑很窄,但确实存在。 journal 把数据也写进日志,彻底没这个问题——代价是写入量翻倍。 反过来,在数据库机器上又常常【故意调成 writeback】:数据库自己有 WAL、自己 fsync,文件系统再套一层日志等于写两遍日志,纯属浪费。这时一致性由数据库负责,文件系统只管别把元数据写坏。 所以【选哪个】的答案不是技术性的,是分工性的:谁更懂这份数据的一致性,谁就该负责到底。

四、fsck:最后一道防线

上面的机制都是【事前防】,fsck 是【事后修】。 它做三件事:① 扫所有 inode,核大小与块引用;② 扫所有目录项,核它们指向的 inode 存不存在;③ 核块位图,找出【被标占却没人引用】与【被引用却没标占】的块。 代价是时间:1000 个文件几秒钟,100 万个文件十几分钟——因为工作量是【个数】不是【字节】。 所以现代文件系统的目标是【让你永远不用跑 fsck】:日志让挂载时几秒钟回放完,写时复制让它根本不需要扫。 但 fsck 仍然是必要的:它是唯一能处理【日志也救不了】的那种残局的东西,比如日志区本身坏了。

五、动手 Demo:文件系统不一致的对策——三种日志模式怎么选

这一节先把三种模式的写入量摆清楚,再算一遍 fsck 的时间随文件数怎么长。

# 三种模式:一次写操作各写几遍
modes = (
    ("writeback", 1, 2, "1.0"),   # 数据 1 次,元数据 2 次
    ("ordered",   1, 2, "1.3"),
    ("journal",   2, 3, "2.2"),
)
for name, d, m, rel in modes:
    print("%-10s 数据 %d 次 元数据 %d 次 相对耗时 %s" % (name, d, m, rel))

# fsck 的耗时随文件数怎么长(按每项 10 微秒估)
for n in (1000, 100000, 1000000):
    sec = n * 2 * 1e-6 * 10   # 每个文件约两项:inode + 目录项
    print(n, "个文件 -> %.2f 秒" % sec)

实跑输出:

--------------------------------------------------------------------
文件系统不一致的对策——三种日志模式怎么选
--------------------------------------------------------------------
① 一次写操作,三种模式各写几遍

  模式          数据写         元数据写     一致性             相对耗时
  ------------------------------------------------------------------------
  writeback     1 次           2 次         可能是旧数据       1.0
  ordered       1 次先落盘     2 次         不会读到旧数据     1.3
  journal       2 次           3 次         数据也保           2.2

  **writeback 最快,但有个恶心的坑**:
  **元数据说【文件已经变长了】,而数据块还没落盘。**
  **这时断电,文件长度对,内容是一堆旧垃圾。**

  **ordered 的做法很聪明**:**数据先落盘,才允许提交元数据**,
  **代价只是【一次额外的等待】。**
  **所以几乎所有发行版默认 ordered。**
  **它用 30% 的代价拿掉了最恶心的那个坑。**

② 那为什么还有人用 journal

  **因为 ordered 有一处管不到**:**追加写。**
  **往文件末尾追加时,如果新数据块里混着【上一次被删文件的残留】,**
  **文件长度会先变长,泄露几个字节的旧内容。**
  **journal 把数据也写进日志,彻底没这个问题**——**代价是写入量翻倍。**

  **在数据库机器上不这么干**:**数据库自己有日志、自己 fsync。**
  **文件系统再套一层日志,等于写两遍日志。**
  **所以数据库服务器常常把文件系统日志调成 writeback,**
  **把一致性交给数据库自己管。**

③ 还有一道防线:fsck

  文件数         要扫的项数     大概多久
  -----------------------------------------
  1000 个        2000 项        0.02 秒
  100000 个      200000 项      2.00 秒
  1000000 个     2000000 项     20.00 秒

  **fsck 要【把整个元数据读一遍】。**
  **所以耗时正比于【文件数】,不是【容量】。**
  **一块存了 100 万个小文件的 1 TB 盘,**
  **fsck 比存了几个大文件的 1 TB 盘慢得多。**

  📋 **Linux 上看当前模式**:
  `tune2fs -l /dev/sda1 | grep -i mount`;
  改则用 `tune2fs -o journal_data_ordered /dev/sda1`。

ordered 用 30% 的代价换掉【读到旧数据】这个坑,所以它是几乎所有发行版的默认。 fsck 的耗时是线性长的,而且基数不小:100 万个文件十几分钟。这就是【为什么要少放小文件】的另一个理由。 注意这里的【相对耗时】是估算值,具体倍数取决于设备;但这三个数的【顺序】是稳定的:writeback 最快,journal 最慢。

📋 Linux 上看当前模式:tune2fs -l /dev/sda1 | grep -i "mount options"; 📋 改模式(要重新挂载才生效):tune2fs -o journal_data_ordered /dev/sda1。

30 秒实验

  1. tune2fs -l /dev/sda1 | grep -i "mount options",看你的机器是哪种。
  2. 想一想:为什么数据库服务器常把文件系统日志调成 writeback?
  3. 算一遍:你的盘上如果有 200 万个小文件,fsck 大概要多久?

常见误区

  1. 【journal 最安全,所以都该用它】。它最慢,而且与数据库自己的 WAL 重复;分工清楚比一味求安全更重要。
  2. 【ordered 保证数据不丢】。它只保证【不会读到旧数据】;数据有没有真的写到盘上,还得应用自己 fsync。
  3. 【fsck 能救回所有东西】。它只能把盘修成自洽;互相矛盾的 inode 会被直接删掉,放进 lost+found。
自测三问(点开对答案) 1. 三种模式的根本差别在哪? —— **在【数据要不要先进日志或先落盘】**;**元数据三者都受日志保护。** 2. writeback 那个【坑】具体是什么? —— **元数据可能比数据先到盘上**,**断电后文件长度对、内容是别人用过的旧数据**——**这是泄露,不只是丢失。** 3. 为什么数据库机器上反而用 writeback? —— **因为数据库自己有 WAL 与 `fsync`**,**文件系统再套一层日志就是写两遍**,**一致性交给更懂业务的那一层负责。**

相关笔记