主题色
250
壁纸模式
壁纸设置
特效设置
固定导航栏
字体选择
文章列表布局
1502 字
4 分钟
检查点(Checkpointing)
2026-06-04

一、为什么需要检查点#

你的系统用了预写日志(WAL)来保证持久性,每次操作先写日志再执行,崩溃后重放日志恢复状态。这很可靠,直到数据库跑了一年:WAL 文件累积了几十个 GB。服务器重启后,重放全部日志需要 20 分钟,期间服务不可用。

更糟的是,WAL 会无限增长。没有机制告诉系统「这些日志已经没用了」,旧日志只能一直留着。磁盘空间和恢复时间都在持续膨胀。

检查点要解决的就是这个问题:定期对当前状态做一次快照,快照完成之前的日志就可以安全删除。崩溃恢复时,加载最近的检查点,只重放之后的日志。检查点越频繁,恢复越快,但每次检查点都有 I/O 开销。这是一个经典的权衡。

二、现实类比#

游戏存档。玩一会儿,按「保存」,如果死了就从最近的存档点重来,而不是从头开始。存档越频繁,丢失的进度越少,但每次存档都需要时间。没有存档的话,每次死亡都要从第一关重新打,和没有检查点时重放全部日志一个道理。

三、核心思想#

检查点在已知时间点捕获当前系统状态的一致性快照。崩溃后,恢复加载最后的检查点,只重放之后记录的操作。没有检查点,基于 WAL 的系统必须在每次重启时重放整个历史,恢复时间会无限增长。检查点将恢复时间限制在最后一个检查点以来的时间间隔内。

flowchart LR subgraph WAL OP1[op1] --> OP2[op2] --> OP3[op3] --> OP4[op4] --> OP5[op5] --> OP6[op6] --> OP7[op7] --> OP8[op8] end CP1["检查点 1"] -.-> OP3 CP2["检查点 2"] -.-> OP6 style CP1 fill:#f9f,stroke:#333 style CP2 fill:#f9f,stroke:#333

没有检查点:恢复需重放 op1-op8(8 次操作)。有检查点:恢复只需加载检查点 2,重放 op7-op8(2 次操作)。检查点之前的日志可以安全截断。

属性值
恢复时间与上次检查点以来的操作数成正比
检查点代价O(state_size) 序列化当前状态
WAL 截断可安全丢弃检查点之前的日志条目
一致性检查点必须捕获一致性快照

四、变体与对比#

模式关系区别
预写日志(WAL)检查点截断 WALWAL 保证持久性,检查点限制恢复时间
写时复制(Copy-on-Write)COW 在不停止写入的情况下实现一致快照COW 是快照技术,检查点是恢复策略
逻辑时钟(Logical Clock)检查点与逻辑时钟位置关联逻辑时钟保证一致性顺序,检查点提供恢复起点
Merkle 树Merkle 树验证检查点完整性Merkle 树是校验工具,检查点是恢复机制

检查点与 WAL 是互补关系:WAL 解决「数据不丢」的问题,检查点解决「恢复不慢」的问题。没有 WAL,检查点之后的操作会丢失;没有检查点,WAL 会无限增长导致恢复时间失控。

五、多语言实现#

package checkpoint
// LogEntry WAL 日志条目
type LogEntry struct {
ID int
Operation string
Key string
Value any
}
// stateSnapshot 检查点快照
type stateSnapshot struct {
state map[string]any
walPosition int // 快照对应的 WAL 位置
}
// CheckpointableStore 带检查点的可恢复存储
type CheckpointableStore struct {
state map[string]any
wal []LogEntry
nextID int
checkpoint *stateSnapshot
}
func NewStore() *CheckpointableStore {
return &CheckpointableStore{
state: make(map[string]any),
nextID: 1,
}
}
// Apply 执行操作并记录到 WAL
func (s *CheckpointableStore) Apply(operation, key string, value any) {
entry := LogEntry{ID: s.nextID, Operation: operation, Key: key, Value: value}
s.nextID++
s.wal = append(s.wal, entry)
s.executeOp(entry)
}
// Get 读取当前状态
func (s *CheckpointableStore) Get(key string) (any, bool) {
v, ok := s.state[key]
return v, ok
}
// TakeCheckpoint 创建检查点:快照当前状态和 WAL 位置
func (s *CheckpointableStore) TakeCheckpoint() {
snap := make(map[string]any, len(s.state))
for k, v := range s.state {
snap[k] = v
}
s.checkpoint = &stateSnapshot{state: snap, walPosition: len(s.wal)}
}
// SimulateCrash 模拟崩溃:清空内存状态
func (s *CheckpointableStore) SimulateCrash() {
s.state = make(map[string]any)
}
// Recover 从检查点 + WAL 恢复,返回重放的条目数
func (s *CheckpointableStore) Recover() int {
if s.checkpoint != nil {
s.state = make(map[string]any, len(s.checkpoint.state))
for k, v := range s.checkpoint.state {
s.state[k] = v
}
replayed := 0
for i := s.checkpoint.walPosition; i < len(s.wal); i++ {
s.executeOp(s.wal[i])
replayed++
}
return replayed
}
// 无检查点:重放整个 WAL
s.state = make(map[string]any)
for _, entry := range s.wal {
s.executeOp(entry)
}
return len(s.wal)
}
func (s *CheckpointableStore) executeOp(entry LogEntry) {
switch entry.Operation {
case "SET":
s.state[entry.Key] = entry.Value
case "DELETE":
delete(s.state, entry.Key)
}
}

六、生产验证#

  • PostgreSQL — checkpointer.c#L206 中 CheckpointerMain 是检查点后台进程。循环等待检查点请求或 checkpoint_timeout(默认 5 分钟),调用 CreateCheckPoint 将所有脏缓冲区刷写到磁盘,写入检查点 WAL 记录,更新 pg_control。崩溃恢复时从最后的检查点开始重放 WAL。
  • Redis — rdb.c#L1880 中 rdbSaveRio 将整个 Redis 数据集序列化到 RDB 文件。Redis fork 一个子进程写入快照而不阻塞主线程。RDB 文件本身是状态检查点,但 Redis 启动恢复不会「加载 RDB 再重放其后 AOF」:loadDataFromDisk 对 AOF 与 RDB 走互斥分支,AOF 开启时只从 AOF 加载(AOF 文件内部可含一段 RDB preamble 作为 base,其后的增量命令才重放),AOF 关闭时才加载 dump.rdb。
  • Apache Flink — 基于 Chandy-Lamport 算法的分布式快照实现精确一次的流处理保证。

七、小结#

何时使用:

  • 数据库崩溃恢复,限制 WAL 重放时间(PostgreSQL、MySQL)
  • 内存缓存持久化,重启后恢复状态(Redis RDB)
  • 流处理,保存处理位置实现精确一次保证(Flink、Kafka)
  • 长时间运行的计算,保存进度在故障后恢复(ML 训练)

何时不用:

  • 无状态服务,没有需要检查点的状态
  • 非常小的状态,WAL 重放时间不到 1 秒时,检查点增加复杂性但收益很小
  • 快速变化的状态,整个状态在检查点之间都变了,快照和重放 WAL 一样昂贵
  • 分布式状态,跨节点协调一致性检查点需要分布式快照协议(Chandy-Lamport)

八、参考资料#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

赞助
检查点(Checkpointing)
https://blog.souloss.cn/posts/programming/system-patterns/system-patterns-checkpointing/
作者
Tsukimi
发布于
2026-06-04
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时