第八十八章:日志之道
斩灵期涉及内核源码:
一
林小源在 ext4 巨树的根部发现了一条暗河。
暗河的水流湍急,水面上漂浮着无数细小的光点。每个光点都在闪烁,像是在记录什么。暗河的入口处立着一块石碑,碑上刻着"JBD2"三个字。
"这是日志,"巨树的声音从头顶传来,"我的记忆之河。"
林小源蹲下来,伸手触碰水面。指尖接触的瞬间,一股信息涌入脑海——他看到了一段完整的操作序列:TxB(事务开始),然后是一连串元数据修改,最后是 TxC(事务提交)。
"每次修改元数据之前,"巨树说,"先把操作记录到这条河里。写完日志,再写实际位置。如果中间出了岔子——比如突然断电——日志还在。重启的时候,内核读日志,重放操作,文件系统就恢复到一致状态。"
林小源看着水面上的光点。每个光点都是一个事务记录,有的已经提交(TxC),有的还在进行中。
"如果崩溃发生在写日志之前呢?"
"没有日志,没有修改,"巨树说,"文件系统保持原状。什么都不丢。"
"如果崩溃发生在写日志之后、写实际位置之前?"
"日志里有完整事务,"巨树说,"重放日志,把修改写到实际位置。数据不丢。"
"如果崩溃发生在写实际位置之后?"
"数据已经安全写入,"巨树说,"日志可以忽略。"
二
林小源沿着暗河走了很远。
河水的颜色在不断变化——有的河段是蓝色的,有的是绿色的,有的是红色的。每种颜色代表一种日志模式。
"蓝色是什么?"他问。
"journal 模式,"暗河中传出一个声音,低沉而谨慎,"元数据和数据都写日志。最安全,但最慢——每次写入都是双份。"
"绿色呢?"
"ordered 模式,"声音变得沉稳了一些,"只对元数据写日志。但有一个规矩——数据必须在元数据之前写入。这样即使崩溃,你看到的要么是旧数据,要么是新数据,不会看到半截新半截旧。"
林小源点了点头。ordered 模式是默认模式——安全性够用,性能也不错。
"红色呢?"
"writeback 模式,"声音变得急促,"只对元数据写日志,数据随便什么时候写。最快,但风险最大——崩溃后可能看到乱序的数据。"
林小源蹲下来,用手指在河边的泥土上画了三个方框:journal 方框里有两层(数据+日志),ordered 方框里数据在前、元数据在后,writeback 方框里只有元数据有日志。
"ordered 是平衡点,"他低声说。
"对,"暗河的声音说,"大部分系统用 ordered。只有对数据完整性要求极高的数据库才会用 journal。对性能要求极高、不太在乎数据完整性的场景才用 writeback。"
三
林小源在暗河的尽头发现了一个巨大的漩涡。
漩涡的中心是一个发光的圆环,圆环上刻着"commit"的字样。所有已提交的事务最终都会流入这个漩涡——从日志空间中释放出来,回到空闲池。
"这就是事务提交,"巨树说,"commit 之后,日志空间可以重用。但如果 commit 之前崩溃——日志还在,可以重放。"
林小源看着漩涡,忽然问:"日志的代价是什么?"
"双份写入,"巨树的声音变得沉重,"每次修改元数据,都要写两次——一次写日志,一次写实际位置。写入性能至少减半。"
"值得吗?"
"当然值得,"巨树说,"没有日志的文件系统,崩溃后需要 fsck 全盘扫描。磁盘越大,扫描越慢——几个小时甚至几天。有了日志,几秒钟就能恢复。"
林小源站起身。他看着暗河——水流不停,光点闪烁,每一个光点都是一份安全保障。日志是文件系统的"后悔药"——代价是性能,换来的是崩溃后的快速恢复。
"安全有代价,"他低声说。
"但值得,"巨树说。
"日志就是给时间做的备份,"他自言自语,"代价是性能,换来的是不怕断电的底气。"
/*
* JBD2 (Journaling Block Device 2) 的工作原理:
*
* 1. 写入日志
* 把要修改的元数据写到日志区
* 标记事务开始 (TxB)
* 写入元数据
* 标记事务提交 (TxC)
*
* 2. 写入实际位置
* 把元数据写到磁盘的实际位置
*
* 3. 释放日志
* 事务完成后,释放日志空间
*
* 崩溃恢复:
* 如果崩溃发生在 1 之后、2 之前:
* 日志中有完整事务,重放日志
* 如果崩溃发生在 2 之后:
* 数据已写到实际位置,忽略日志
*
* 日志模式:
* journal — 元数据和数据都写日志(最安全,最慢)
* ordered — 只元数据写日志,数据先写(默认)
* writeback — 只元数据写日志(最快,最不安全)
*/
printf("=== 日志 — 崩溃恢复的保障 ===\n\n");
printf("日志的工作流程:\n\n");
printf("1. 写入日志:\n");
printf(" ┌─────┐\n");
printf(" │ TxB │ 事务开始\n");
printf(" │ ... │ 元数据操作\n");
printf(" │ TxC │ 事务提交\n");
printf(" └─────┘\n\n");
printf("2. 写入实际位置:\n");
printf(" 日志 → 磁盘实际位置\n\n");
printf("3. 释放日志:\n");
printf(" 事务完成,释放空间\n\n");
printf("--- 崩溃场景 ---\n");
printf("场景 1: 崩溃在写日志之前\n");
printf(" 没有日志,没有修改\n");
printf(" 文件系统保持原状\n\n");
printf("场景 2: 崩溃在写日志之后\n");
printf(" 日志中有完整事务\n");
printf(" 恢复时重放日志\n\n");
printf("场景 3: 崩溃在写实际位置之后\n");
printf(" 数据已安全写入\n");
printf(" 忽略日志\n\n");
printf("--- 日志模式 ---\n");
printf("journal (最安全):\n");
printf(" 元数据和数据都写日志\n");
printf(" 最慢\n\n");
printf("ordered (默认):\n");
printf(" 数据先写,元数据写日志\n");
printf(" 保证数据在元数据之前\n\n");
printf("writeback (最快):\n");
printf(" 只元数据写日志\n");
printf(" 数据可能乱序\n\n");
printf("--- 挂载选项 ---\n");
printf("data=journal\n");
printf("data=ordered\n");
printf("data=writeback\n");#include <stdio.h>
/*
* JBD2 (Journaling Block Device 2) 的工作原理:
*
* 1. 写入日志
* 把要修改的元数据写到日志区
* 标记事务开始 (TxB)
* 写入元数据
* 标记事务提交 (TxC)
*
* 2. 写入实际位置
* 把元数据写到磁盘的实际位置
*
* 3. 释放日志
* 事务完成后,释放日志空间
*
* 崩溃恢复:
* 如果崩溃发生在 1 之后、2 之前:
* 日志中有完整事务,重放日志
* 如果崩溃发生在 2 之后:
* 数据已写到实际位置,忽略日志
*
* 日志模式:
* journal — 元数据和数据都写日志(最安全,最慢)
* ordered — 只元数据写日志,数据先写(默认)
* writeback — 只元数据写日志(最快,最不安全)
*/
int main() {
printf("=== 日志 — 崩溃恢复的保障 ===\n\n");
printf("日志的工作流程:\n\n");
printf("1. 写入日志:\n");
printf(" ┌─────┐\n");
printf(" │ TxB │ 事务开始\n");
printf(" │ ... │ 元数据操作\n");
printf(" │ TxC │ 事务提交\n");
printf(" └─────┘\n\n");
printf("2. 写入实际位置:\n");
printf(" 日志 → 磁盘实际位置\n\n");
printf("3. 释放日志:\n");
printf(" 事务完成,释放空间\n\n");
printf("--- 崩溃场景 ---\n");
printf("场景 1: 崩溃在写日志之前\n");
printf(" 没有日志,没有修改\n");
printf(" 文件系统保持原状\n\n");
printf("场景 2: 崩溃在写日志之后\n");
printf(" 日志中有完整事务\n");
printf(" 恢复时重放日志\n\n");
printf("场景 3: 崩溃在写实际位置之后\n");
printf(" 数据已安全写入\n");
printf(" 忽略日志\n\n");
printf("--- 日志模式 ---\n");
printf("journal (最安全):\n");
printf(" 元数据和数据都写日志\n");
printf(" 最慢\n\n");
printf("ordered (默认):\n");
printf(" 数据先写,元数据写日志\n");
printf(" 保证数据在元数据之前\n\n");
printf("writeback (最快):\n");
printf(" 只元数据写日志\n");
printf(" 数据可能乱序\n\n");
printf("--- 挂载选项 ---\n");
printf("data=journal\n");
printf("data=ordered\n");
printf("data=writeback\n");
return 0;
}道藏笔记
内核启示
日志是文件系统的一致性保障机制。原理不复杂:修改元数据之前,先把操作记到日志里;写完日志,再写实际位置;最后释放日志空间。崩溃发生在任何一步都有对应的恢复策略——日志写完之前崩溃,什么都没改,文件系统原样;日志写完之后崩溃,重放日志就能恢复一致。
日志有三种模式。journal 最安全,元数据和数据都写日志,但每次写入都是双份,性能最差。ordered 是默认模式,只对元数据写日志,但规矩是数据必须在元数据之前写入——这样崩溃后你看到的要么是旧数据要么是新数据,不会半新半旧。writeback 最快,只对元数据写日志,数据随便什么时候写,风险也最大。
没有日志的文件系统,崩溃后需要 fsck 全盘扫描,磁盘越大扫越慢。有了日志,几秒钟就能恢复。代价是写入性能至少减半——但对需要数据安全的场景来说,这笔账算得过来。
日志之试
日志之道中,事务最终要落盘提交;本章代码和概念里反复出现的提交动作叫什么?