Log:一种被低估的计算机科学基础抽象

在计算机科学中,log(日志)远不止是"打印调试信息"那么简单。从数据库的 WAL 到分布式系统的共识协议,从版本控制系统到区块链,log 作为一种 append-only 的有序记录序列,是贯穿整个计算机科学发展史的核心抽象之一。

LinkedIn 的前首席工程师 Jay Kreps 在其著名文章 “The Log: What every software engineer should know about real-time data’s unifying abstraction” 中指出:log 是一种比消息队列、数据库、文件系统更基础的抽象——后者都可以建立在 log 之上。

本文尝试梳理 log 这一抽象在不同技术领域中的演化脉络。

数据库中的 Log:WAL 与 Binlog

WAL(Write-Ahead Logging)

WAL 是数据库实现 ACID 特性的基石。其核心思想是:在修改数据页之前,先将修改操作写入日志

WAL 的工作流程:

  1. 事务开始时,将修改操作(redo log entry)追加写入日志文件
  2. 日志写入成功后(fsync 到磁盘),才将修改应用到数据页(可以延迟刷盘)
  3. 事务提交时,确保所有相关日志已持久化

这种设计的核心权衡是:将随机写转化为顺序写。磁盘的顺序写性能远高于随机写(机械硬盘上可达百倍差距),因此先顺序写日志、再异步刷数据页,整体吞吐量大幅提升。

PostgreSQL 的 WAL、InnoDB 的 redo log、SQLite 的 WAL 模式,都是这一思想的具体实现。

存储引擎中的 Journal 实现

Linux 中的日志文件系统

文件系统要解决的一个关键问题是防止掉电或系统崩溃造成数据损坏。

一个文件既包括元数据,又包括真正的用户数据。文件写入时这两部分数据都需要被写入,而写文件不是原子操作——如果其中任何一个步骤被打断,就会造成数据的不一致或损坏。

日志文件系统(Journal File System)的原理是:在进行写操作之前,把即将进行的各个步骤(称为 transaction)事先记录下来,保存在文件系统上单独开辟的一块空间上,这就是所谓的日志(journal),也被称为 write-ahead logging(WAL)。日志保存成功之后才进行真正的写操作,把文件系统的元数据和用户数据写进硬盘(称为 checkpoint)。这样万一写操作的过程中掉电,下次挂载文件系统之前把保存好的日志重新执行一遍就行了(称为 replay),避免了数据损坏场景。

结论:到达 checkpoint 意味着它之前的 log 不需要 replay 了

MySQL InnoDB 的 redo log 结构

InnoDB 的 redo log 是固定大小的循环结构,例如一组 4 个文件,每个 1GB,总共可记录 4GB 的操作。从头开始写,写到末尾就回到开头循环写:

  • write pos:当前记录的位置,一边写一边后移
  • checkpoint:当前要擦除的位置,也是往后推移并且循环的。擦除前要把对应的 dirty page 更新到数据文件

write pos 和 checkpoint 之间的是空闲区域,可以用来记录新操作。如果 write pos 追上 checkpoint,就不能再执行新的更新,必须停下来推进 checkpoint。

Binlog(Binary Log)

MySQL 的 binlog 与 redo log 不同。Redo log 是存储引擎层面的物理日志(记录"哪个数据页的哪个偏移量做了什么修改"),而 binlog 是 MySQL Server 层面的逻辑日志(记录"执行了什么 SQL 语句"或"哪些行发生了什么变化")。

Binlog 的三种格式:

  • Statement:记录原始 SQL 语句。体积小,但在主从复制时可能因为执行环境差异导致不一致
  • Row:记录每一行数据的变化前后值。体积大,但复制结果确定性强
  • Mixed:MySQL 自动选择 Statement 或 Row 格式

Binlog 的核心应用场景是 主从复制(replication)和 数据恢复(point-in-time recovery)。从 log 的视角看,binlog 本质上是一个 可重放的操作序列——将这个序列在另一个 MySQL 实例上重放,就实现了数据复制。

Log 在数据库中的模式提炼

WAL 和 binlog 揭示了 log 在数据库中的两个核心角色:

  1. 崩溃恢复:通过重放 log 恢复未持久化的数据(redo),或撤销未提交的事务(undo)
  2. 状态复制:通过传播 log 使多个节点达到相同的状态

这两个角色的本质是同一个:log 是状态变化的权威记录,任何状态都可以通过重放 log 来重建

版本控制中的 Log:Git 与 DAG

从线性日志到 DAG

早期的版本控制系统(如 CVS、SVN)使用线性的版本号序列来记录变更历史。每个版本是前一个版本的唯一后继,形成一条直线。

Git 的革命性设计在于引入了 DAG(Directed Acyclic Graph,有向无环图) 作为版本历史的数据结构。在 Git 中:

  • 每个 commit 是一个节点,包含指向一个或多个父 commit 的指针
  • 分支(branch)只是一个指向某个 commit 的可移动指针
  • 合并(merge)产生一个拥有两个父节点的 commit

DAG 相比线性日志的优势在于:它天然支持 并行开发非线性历史。多个开发者可以在各自的分支上独立工作,最终通过 merge 汇合。

Git 的对象模型

Git 的底层存储本质上也是一种 log。Git 仓库中的所有数据都存储为四种对象:

  • blob:文件内容
  • tree:目录结构,指向 blob 和子 tree
  • commit:一次提交,指向一个 tree 和父 commit(s)
  • tag:指向某个 commit 的命名引用

每个对象通过其内容的 SHA-1 哈希值来寻址。这种 内容寻址存储(content-addressable storage)保证了数据的完整性——任何篡改都会导致哈希值变化,从而被检测到。

从 Git 到 Log 的本质

Git 的 commit 历史本质上是一个 不可变的、仅追加的操作日志。每个 commit 记录了"谁在什么时间对代码做了什么修改",而当前代码库的状态就是所有 commit 按拓扑序重放的结果。

这与数据库的 WAL 是同构的:WAL 记录数据变更,重放 WAL 可以重建数据库状态;Git log 记录代码变更,重放 commit 历史可以重建代码库的任意历史状态。

分布式系统中的 Log:共识与复制

复制状态机(Replicated State Machine)

分布式系统中最重要的 log 应用是 复制状态机(Replicated State Machine)模型。其核心思想是:

如果多个节点以相同的初始状态开始,按相同的顺序执行相同的操作序列(log),那么它们最终会到达相同的状态。

因此,分布式一致性问题被转化为:如何让多个节点就 log 的内容和顺序达成共识

Raft、Paxos、ZAB 等共识算法的核心工作,就是确保集群中所有节点拥有相同的、有序的 log。一旦 log 达成共识,每个节点独立地将 log 应用到自己的状态机上,就自然实现了状态一致。

Kafka:Log 作为基础设施

Apache Kafka 将 log 从数据库内部的实现细节提升为一种 独立的基础设施。Kafka 的每个 partition 本质上就是一个 append-only 的有序 log:

  • 生产者向 log 尾部追加消息
  • 消费者从 log 的某个 offset 开始顺序读取
  • 消息被持久化到磁盘,可以被多个消费者独立消费

Kafka 的设计证明了 Jay Kreps 的论点:log 可以作为连接不同系统的通用集成层。数据库的变更通过 CDC(Change Data Capture)写入 Kafka log,下游的搜索引擎、缓存、数据仓库各自消费这个 log 来维护自己的物化视图。

Saga:分布式事务中的补偿日志

从 2PC 到 Saga

在分布式事务领域,传统的两阶段提交(2PC)要求所有参与者在事务期间持有锁,这在微服务架构中代价过高。

Saga 模式提供了一种基于 补偿日志 的替代方案。一个 Saga 由一系列本地事务组成,每个本地事务都有对应的补偿事务:

1
2
T1 → T2 → T3 → ... → Tn
C1C2C3 ← ... ← Cn

如果 Tk 执行失败,系统按逆序执行 C(k-1)、C(k-2)、…、C1 来撤销已完成的操作。

Saga 与 Log 的关系

Saga 的执行过程本质上维护了一个 操作日志

  1. 每个本地事务的执行结果被记录到 Saga log 中
  2. Saga 协调器根据 log 中的记录决定下一步是继续前进还是开始补偿
  3. 补偿操作本身也被记录到 log 中,确保补偿过程的幂等性和可恢复性

Saga log 与数据库的 undo log 在概念上是同构的:数据库的 undo log 记录事务的反向操作以支持回滚,Saga log 记录补偿事务以支持分布式回滚。区别在于,数据库的 undo log 在单机事务中运作,而 Saga log 跨越多个服务。

Saga 有两种协调模式:

  • 编排式(Choreography):每个服务监听事件并决定下一步动作,log 分散在各个服务的事件流中
  • 协调式(Orchestration):一个中央协调器维护 Saga 的执行 log,统一调度各个步骤

区块链:去中心化的不可篡改日志

区块链的 Log 本质

区块链从数据结构的角度看,就是一个 去中心化的、不可篡改的 append-only log。每个区块包含:

  • 一批交易记录(log entries)
  • 前一个区块的哈希值(形成链式结构)
  • 工作量证明或权益证明(共识机制的产物)

区块链与传统 log 的关键区别在于 信任模型

维度 传统 Log(WAL/Binlog) 区块链
信任假设 信任单一运维方 不信任任何单一参与方
共识机制 不需要(单机)或 Raft/Paxos(分布式) PoW/PoS/BFT
写入成本 低(磁盘 I/O) 高(计算或质押)
不可篡改性 依赖访问控制 密码学保证

从 Git 到区块链

Git 和区块链在数据结构上有显著的相似性:

  • 都使用 哈希链 来保证历史记录的完整性
  • 都是 append-only 的(Git 的 rebase 和 force push 本质上是创建新的 commit 链,旧链仍然存在直到被 GC)
  • 都通过 内容寻址 来引用数据

区别在于:Git 允许分叉(fork)和变基(rebase),因为它运行在信任环境中;区块链则通过共识机制确保全网只有一条权威链(最长链规则),因为它运行在不信任环境中。

Log 的统一视角

回顾以上所有场景,可以提炼出 log 作为计算机科学基础抽象的核心特征:

特征 说明
Append-only 只在尾部追加,不修改已有记录
有序性 每条记录有确定的位置(offset/序号/时间戳)
可重放性 从头重放 log 可以重建任意时刻的状态
不可变性 已写入的记录不可修改(至少在逻辑上)

这四个特征使得 log 成为以下问题的通用解决方案:

  • 持久化:先写 log 再更新状态(WAL)
  • 复制:传播 log 使多个副本保持一致(binlog replication、Raft)
  • 审计:log 天然记录了"谁在什么时间做了什么"(Git、区块链)
  • 解耦:生产者写 log,消费者按需读取(Kafka)
  • 恢复:正向重放恢复状态(redo),逆向重放撤销操作(undo/Saga)

正如 Pat Helland 在论文 “Immutability Changes Everything” 中所论述的:当存储足够便宜时,不可变的 append-only 数据结构(即 log)会成为系统设计的默认选择,因为它从根本上消除了并发修改带来的复杂性。

参考资料