AI 资讯 知识库 后端技术操作系统内功 · 第 23 篇|文件系统日志:ext4 的三种挂载模式与掉电一致性

操作系统内功 · 第 23 篇|文件系统日志:ext4 的三种挂载模式与掉电一致性

崩溃一致性问题:一次写操作要碰几处磁盘

一个看似简单的文件追加,在磁盘上要更新多个位置:文件的数据块、inode 里的文件大小、块位图(哪个块被占用了)、目录项。这些写入无法原子地完成——磁盘一次只能持久化一个扇区。写到一半断电,文件系统就处在「中间状态」:数据写了但大小没更新(读回来是旧长度),或块位图标了占用但 inode 没指向它(空间泄漏)。这就是崩溃一致性(crash consistency)问题,文件系统日志是它的工业解。

解法思路与数据库 WAL(第 21 篇)完全同源:先把要做的修改记入日志并保证日志落盘,再执行实际修改。断电重启后,文件系统重放日志中已提交的事务,把未完成的部分补齐或丢弃——任何时刻磁盘上要么是修改前、要么是修改后的完整状态,中间态只存在于日志的记录里。

sequenceDiagram
    participant FS as ext4
    participant J as 日志区(journal)
    participant D as 数据/元数据区
    FS->>J: 1. 写入事务记录(要改什么)
    FS->>J: 2. 提交记录落盘(commit block)
    Note over J: 断电若无 commit block → 重放时丢弃该事务
    FS->>D: 3. 实际修改数据/元数据
    FS->>J: 4. checkpoint 后释放日志空间

三种日志模式:元数据与数据分别对待

日志「记什么」有三种策略,对应 ext4 的三个挂载选项——差异全在文件数据本身是否进日志:

data=journal(最重):数据与元数据都写日志,每次写要落盘两次(日志一次、最终位置一次)。一致性最强,吞吐最差。现代硬件上很少有正当理由用它——需要这种强度的直接上数据库或 CoW 文件系统。

data=ordered(默认):元数据进日志,数据不进;但内核保证先刷数据、再提交元数据。效果是断电后要么看到完整的新文件,要么看到没有新文件的旧状态——不会出现「文件大小变长了、内容却是垃圾」的组合。ext4 十几年来的默认值,是「安全与吞吐的公共交集」。

data=writeback(最快):元数据进日志,数据何时落盘不保证顺序。断电后可能看到「元数据已更新、数据内容是旧值或未初始化」的文件。只适合数据可重建的场景(缓存盘、可重新下载的媒体文件)。

模式 数据进日志 顺序保证 断电最坏情况 吞吐
journal 是 完整 无 最低
ordered 否(但先于元数据刷) 数据先于元数据 新文件不存在(干净) 中
writeback 否 无 文件存在但内容旧/垃圾 最高

注意一个普遍误解的修正:三种模式下元数据都有日志——目录结构、文件名、权限在断电后都不坏。差别只在文件内容。所以「ext4 日志能防什么」的精确答案是:目录树永远一致,文件内容的保证取决于模式。

「数据为零」的经典事故:没有 fsync 的 append

ordered 模式的保证有明确的边界——它只保证元数据提交时数据已在盘上,但如果应用从未 fsync,元数据本身可以无限期不提交(第 21 篇的 writeback 节奏)。于是出现著名的事故剧本:应用 append 写日志文件,从不 fsync,进程崩溃(数据还在 Page Cache,没事);三十秒后内核自动回写,也没事;直到机器断电——重启后发现日志文件「存在,大小正确,内容全为零」。

为什么是零?ext4 的延迟分配(delayed allocation):写入时内核只记录「这个偏移要写这些数据」,物理块位置等到回写时才分配(尽量攒大块顺序分配)。断电时若元数据(含大小)因某种路径被持久化、而数据块尚未落盘,读回来就是空洞——表现为零或乱码。ext4 在 2009 年由 Linus 亲自合入过一个补丁:对已存在的文件做 append 时,自动把「新大小」的元数据更新推迟到数据落盘之后——堵住了最常见的触发路径,但这个保证只在「文件已存在」的 append 上成立,新建文件仍需 fsync(第 21 篇讲过的目录 fsync 陷阱)。

工程结论一句话:文件系统的日志模式给的是「文件系统自身结构不坏」的保证,应用数据的持久性永远由 fsync 的调用纪律决定。两者是不同层级的承诺,混为一谈是数据丢失事故的常见起点。

日志的工程参数:大小、提交间隔与 Barrier

日志大小(mke2fs 时可指定)决定两个性能特征:事务越大越多,日志越需要空间;日志满会强制触发 checkpoint(把日志里已提交的修改刷到最终位置腾地方),表现为周期性的写放大。日志长期偏满的文件系统,dmesg 里能看到相关的延迟告警。

提交间隔(commit= 挂载选项,默认 5 秒)控制日志事务多久强制落盘一次——这是文件系统层的「30 秒窗口」的更细粒度版本,调小增加 IO、调大增加丢失窗口。

写屏障(barrier=1,默认开):确保日志提交记录真的到达非易失介质(而不是死在易失的写缓存里),防的是「断电后日志区自身半途而废」。带电池写缓存(BBU)的 RAID 卡可以安全地关掉 barrier 换性能(barrier=0 或 nobarrier),普通消费级 SSD 绝不能关——这是 RAID 卡宣传里「带电容保护」的直接技术动因。

# 查看当前挂载模式与日志状态
dumpe2fs -h /dev/sda1 | grep -E 'Filesystem features|Journal size'
mount | grep sda1          # 看 data= 选项
tune2fs -l /dev/sda1 | grep -i journal
# 人为验证掉电一致性:dm-flakey 或 dm-log-writes 可以模拟断电点

崩溃一致性测试:怎么证明「不坏」

文件系统的崩溃保证不是靠信仰,是靠测试框架打出来的。dm-log-writes 记录每一次块级写并支持「回放到任意一点 + fsck 验证」,dm-flakey 让设备在指定时刻表现为掉电。xfs/ext4/btrfs 的 CI 都跑这类测试;数据库系统(如 MySQL 的 mtr 崩溃测试)在上层做同样的游戏。理解这套机制的意义在于:当存储厂商声称「掉电安全」时,你应该追问它用的是 journal、CoW 还是校验+重放,以及在哪种模式下经过哪套崩溃测试——这句追问能过滤掉九成营销话术。

顺带把 CoW 文件系统放到坐标系里:btrfs/ZFS 不用「先写日志再原地改」的两段式,而是永不覆写(copy-on-write:新数据写新位置,元数据树原子切换),一致性与数据校验和能力一体。代价是随机写放大与碎片化。日志适合「就地更新 + 快恢复」,CoW 适合「校验 + 快照」,快照文件系统(ext4 的快照支持)与数据库页级校验和(InnoDB 的 checksum)是两个阵营互相借鉴的产物。

XFS 与 ext4:两条日志路线的现实对比

生产 Linux 的两个主力文件系统对日志的实现路径不同,选型差异由此而来。ext4 走「日志加块分配器」路线:JBD2 日志模块管理元数据事务(ordered 模式),延迟分配尽量把块分配推迟到回写时攒大段——适合中小文件混合负载,文件删除与 fsck 速度好, decades 的成熟度使它是发行版默认。XFS 走「B树加区间分配」路线:元数据全部 B 树化,分配器按Extent 粒度做区间规划,并发创建大量文件与超大单文件(TB 级)的扩展性显著更好,日志逻辑与元数据更紧凑——大数据、媒体库、容器镜像层这些「海量文件或巨型文件」场景的常见选择。两者近年互相吸收(ext4 借了 extent 树与延迟分配,XFS 补了在线碎片整理的易用性),纯性能差距已缩小到负载敏感的个位数百分比,选型的决定因素更多在生态:XFS 不支持收缩(只能扩),ext4 支持离线收缩;XFS 的 reflink(写时复制文件克隆,容器镜像与快照的加速器)比 ext4 的实现早且成熟。docker 与 Kubernetes 节点的文件系统选 XFS 加 reflink 已是主流实践。一个容易踩的坑:XFS 的默认挂载选项与 ext4 不同(如 inode64),跨系统迁移配置文件时不要照搬 mount 选项——两家的默认值各自经过调优,盲目统一反而劣化。

fsck 与日志的关系:一致性检查变轻了吗

日志机制的隐性红利在「断电后的恢复成本」。没有日志的文件系统(ext2 时代)断电后必须全量 fsck:扫描全部 inode 与块位图核对一致性——TB 级盘上的 fsck 以小时计,历史上「服务器意外断电后半天不能上线」的事故大多源于此。有了日志,恢复只需重放日志里未 checkpoint 的事务——日志区通常几百 MB,重放秒级完成;全量 fsck 退化为「日志区损坏」时的最后手段。但三个边界要清楚。其一,日志保证的是文件系统结构一致,不保证应用数据完整(第 23 篇正文的主线)——fsck 快了不等于数据没丢,应用的 fsync 纪律仍是数据安全的唯一防线。其二,fsck 依然存在,只是触发频率大幅降低:XFS 在元数据不一致时进入「需要手工 xfs_repair」状态(mount 会拒绝并提示),ext4 的 e2fsck -p 快速修复路径处理小损伤。其三,在线修复能力是加分项:XFS 的 xfs_repair 支持对 unmount 后的修复,btrfs/ZFS 的校验和让「哪些数据坏了」可精确定位——ext4/XFS 没有 per-block 校验和,静默损坏(bit rot)靠 RAID 层或应用层校验兜底,这是 CoW 文件系统的真实卖点。运维口径:预期内的断电重启(日志重放)不需要人工干预,mount 报「建议 fsck」时按提示处理即可;但监控里要盯 fsck 的耗时趋势——它异常增长往往意味着硬件(盘/线缆/HBA)在悄悄劣化。

小结

文件系统日志用 write-ahead 的方式解决崩溃一致性:先把修改记入日志并保证提交,再执行原地更新,断电后重放恢复。ext4 的三种模式差异全在文件数据是否受保护——ordered 默认平衡,writeback 接受内容陈旧换吞吐,journal 双写最强最慢。延迟分配带来的「append 变零」事故说明:文件系统保证结构不坏,应用持久性靠 fsync 纪律,两层承诺不可混用。barrier 选项与非易失写缓存的关系、崩溃测试框架的存在,把「掉电安全」从口号还原为可验证的工程属性。下一篇看文件系统的统一抽象层 VFS——「一切皆文件」的代价与红利。

读者留言

COMMENTS 暂无
仅本站原创文章开放留言 · 请勿留下手机号、邮箱等个人信息

还没有留言,来说第一句?