崩溃一致性、日志与磁盘调度¶
一次高层文件操作可能修改多个持久化块。若系统在中间断电,部分写入已经落盘、部分尚在缓存,文件系统可能进入“每个块单独看都合法,组合起来却矛盾”的状态。崩溃一致性研究怎样定义并限制这种中间状态。
最小问题:创建文件¶
创建 /dir/a 可能需要:
- 分配 inode。
- 初始化 inode 元数据。
- 在目录数据中加入名字
a。 - 更新空闲 inode 位图。
- 更新目录时间戳。
若位图先写盘,目录项没写盘,inode 可能泄漏;若目录项先落盘,inode 尚未初始化,目录会指向垃圾。单个原子扇区写无法覆盖跨多个块的事务。
写入顺序为什么不可靠¶
即使应用按顺序调用 write,数据仍可能经过:
- 用户态缓冲。
- 页缓存。
- 文件系统写回队列。
- 块层重排与合并。
- 设备控制器缓存。
程序顺序、内核提交顺序和非易失介质上的持久化顺序不是同一件事。需要屏障、flush/FUA 类设备语义和文件系统协议共同建立顺序。
崩溃一致性的目标¶
常见目标不是保证最后一次调用的所有数据都存在,而是保证恢复后文件系统元数据自洽,并明确哪些已确认操作必须持久化。
应用还要区分:
- 原子可见性:其他观察者看到旧状态或新状态,不看到半状态。
- 持久性:崩溃恢复后新状态仍存在。
- 顺序性:A 的持久化发生在 B 之前。
一次操作对其他进程可见,不表示已经持久化。
写前日志¶
日志把一次更新作为事务记录。简化协议:
- 把将要执行的更新写入日志。
- 确认日志事务完整持久化。
- 把更新写到最终位置 checkpoint。
- 标记日志空间可复用。
恢复时:
- 完整提交的事务可重放。
- 未完整提交的事务忽略或回滚。
核心不变量是:最终位置更新不能在可恢复日志之前被视为持久提交。
日志模式¶
只记录元数据¶
日志记录 inode、位图、目录项等元数据,文件数据直接写最终位置。开销较低,恢复后结构一致,但最近文件内容可能是旧数据或由具体排序语义决定。
有序数据模式¶
数据不写入日志,但要求新数据在相关元数据提交前写到最终位置,避免元数据指向未初始化块。它提供比纯元数据日志更强的常见安全性,但仍不等同于数据事务。
数据日志¶
数据和元数据都先写日志,语义更强,写放大和日志带宽成本也更高。
不同文件系统模式与接口保证不同,不能只凭“使用 journaling”推断应用数据已经安全。
事务批处理¶
多个文件操作可以合并进一个日志事务,摊薄提交和刷盘成本:
批量越大,吞吐通常越好,但单个更新等待提交的延迟和崩溃时可能丢失的最近窗口也增大。
写时复制文件系统¶
写时复制方案不覆盖当前树中的块,而是写新数据块、新元数据节点,最后原子切换根指针到新版本:
优点包括快照、校验和与避免原地部分覆盖。代价是碎片、写放大、空间回收和引用计数/树更新复杂度。日志与 COW 不是简单的新旧替代,具体系统可能组合使用多种技术。
fsync、重命名与应用协议¶
应用安全保存配置的常见模式:
- 在同目录创建临时文件。
- 写入完整新内容。
- 同步临时文件数据和必要元数据。
- 原子重命名覆盖目标名字。
- 若需保证目录项持久化,再同步目录。
精确要求依操作系统和文件系统而异,应以目标环境文档和故障测试为准。仅调用 close 通常不表示强制持久化。
一致性检查¶
传统 fsck 扫描元数据,重新建立位图、链接计数和目录可达性等不变量。它无需日志也能修复许多结构错误,但大文件系统全盘扫描耗时长,且无法凭空恢复应用最新语义。
日志把恢复工作限制到近期事务,显著缩短启动恢复时间,但仍可能需要后台检查、校验和或人工处理介质损坏。
HDD 上的调度动机¶
机械磁盘访问时间近似由寻道、旋转等待和传输组成:
随机请求中前两项常占主导。调度器重排请求可以减少磁头移动:
- FCFS:公平直观,寻道可能很差。
- SSTF:选择最近请求,平均寻道低,远端请求可能饥饿。
- SCAN:磁头像电梯一样沿一个方向服务,再反向。
- C-SCAN:只在一个方向服务,返回时不处理,等待更均匀。
请求合并把相邻块合成更大传输,提高顺序带宽。
SSD 与 NVMe 改变了什么¶
SSD 没有机械寻道,但仍有:
- 闪存页读取与更大擦除块。
- 闪存转换层地址映射。
- 垃圾回收、磨损均衡和写放大。
- 内部并行通道和队列深度。
NVMe 支持多提交/完成队列和高并行,传统“减少磁头移动”的算法不再是核心,调度更关注公平、合并、优先级和设备并行度。把 HDD 时代结论原样套到 SSD 会误判。
队列深度与延迟¶
向设备保持多个未完成请求能利用内部并行并提高吞吐,但排队时间会增加尾延迟。Little 定律给出稳态直觉:
其中 \(L\) 是系统中平均请求数,\(\lambda\) 是吞吐率,\(W\) 是平均停留时间。盲目增加队列深度可能只把等待从应用移到设备队列。
RAID 与故障边界¶
多盘阵列可通过镜像或校验提高可用性和吞吐,但不是备份:误删除、软件错误和勒索修改会同步传播。写洞、重建期间第二故障和潜在扇区错误也要考虑。
端到端完整性还需要校验和、错误报告、冗余、备份和恢复演练配合。
常见误区¶
write返回成功不一定已经持久化。- 日志文件系统不一定记录用户数据。
- SSD 没有寻道,不等于所有访问延迟相同。
- 更深设备队列提高吞吐,不等于改善单请求延迟。
- 原子重命名保证命名切换原子,不自动保证新内容已经落盘。
自测¶
- 创建文件为何不是单个块的原子更新?
- 写前日志中的“write-ahead”具体要求什么顺序?
- 元数据日志与数据日志的故障保证有何差异?
- SCAN 为什么比 SSTF 更不易让远端请求饥饿?
- SSD 上为什么仍需要 I/O 调度和队列管理?
参考思路
创建涉及 inode、目录和位图。日志必须在最终位置更新被提交前持久可恢复。只记元数据保证结构,数据日志还覆盖文件内容事务。SCAN 有规则地扫过整个地址范围。SSD 仍有并行度、公平、写放大和队列延迟问题。