2. Checkpoint 概念:什么是 Checkpoint?全局状态与局部状态,一致性快照
好,咱们进入正题。上一章聊了容错的必要性,这一章我们得搞清楚一个核心工具——Checkpoint。说白了,Checkpoint 就是给分布式系统拍一张「合影」,记录下某个时刻所有人的状态。万一后面有人挂了,咱们能靠这张合影把系统恢复到那个时刻,接着干活。
我在早期做流计算引擎的时候,对 Checkpoint 的理解就停留在「存个快照」上。结果线上出了故障,恢复后发现数据对不上了……嗯,那时候我才意识到,Checkpoint 不是随便存存就行的,它背后藏着「一致性」这个硬骨头。
2.1 什么是 Checkpoint?
Checkpoint,中文常翻译为「检查点」或「快照点」。它的本质是:在系统运行过程中,将当前的处理状态持久化到一个可靠的存储中。这样当系统崩溃重启后,可以从这个点继续执行,而不是从头开始。
核心价值:Checkpoint 把无限长的计算历史,切分成一个个可恢复的「段」。你想想看,没有 Checkpoint,一次跑 7 天的任务,第 6 天挂了就得重跑 6 天——这谁受得了?
我个人习惯把 Checkpoint 比作「游戏存档」。你打一个超长的 RPG,每过一个大 BOSS 就存个档。万一被小怪打死了,读档回到 BOSS 门口,而不是从新手村重来。分布式系统里的 Checkpoint 就是这个道理,只不过存档的不是你的装备,而是全局状态。
2.2 全局状态与局部状态
要理解 Checkpoint,得先分清楚两个概念:局部状态和全局状态。
2.2.1 局部状态
局部状态,就是单个节点(进程)自己维护的状态。比如:
- 一个 Kafka Consumer 当前消费到的 offset
- 一个 Flink Operator 内部缓存的聚合值
- 一个数据库节点内存中的事务日志
每个节点对自己的局部状态了如指掌。但问题来了——单个节点的状态是片面的。你只知道自己的 offset,但不知道下游节点已经处理到哪条消息了。
我的经验:曾经有个同事只对每个节点单独做 Checkpoint,恢复的时候各节点从各自的存档点重启。结果 A 节点恢复到 offset 100,B 节点恢复到 offset 120,中间那 20 条消息要么丢了要么重复处理了。这就是只做局部 Checkpoint 的坑。
2.2.2 全局状态
全局状态,是整个分布式系统在某一时刻的完整快照。它包含了所有节点的局部状态,以及所有正在传输中的消息(也就是通道里的数据)。
为什么通道里的消息也要算进去?我给你举个例子:
- 节点 A 刚发了一条消息给节点 B
- 这条消息还在网络上飞着,B 还没收到
- 如果只记录 A 和 B 的局部状态,这条「在途消息」就丢了
所以,一个真正的全局状态 = 所有节点的局部状态 + 所有通道中的消息。
| 维度 | 局部状态 | 全局状态 |
|---|---|---|
| 范围 | 单个节点 | 整个系统 |
| 包含内容 | 节点内存/磁盘数据 | 所有节点状态 + 通道消息 |
| 一致性 | 节点内一致 | 跨节点一致 |
| 恢复难度 | 低,但可能不一致 | 高,但保证正确 |
| 典型用途 | 单机容错 | 分布式容错 |
2.3 一致性快照
好,现在问题来了:怎么拍一张「全局合影」,才能保证恢复后系统状态是一致的?
一致性快照,学术上叫 Consistent Snapshot。它的定义很严格:一个全局快照,如果它对应的系统状态,在某个合法的全局时间点上是真实存在的,那么这个快照就是一致的。
听着有点绕?我换个说法:
- 你拍快照的时候,不能拍到「A 已经发了消息,但 B 还没收到」这种中间态
- 要么拍到「A 没发,B 没收」
- 要么拍到「A 发了,B 也收到了」
- 不能拍到「A 发了,B 没收」——因为这种状态在真实运行中可能只存在一瞬间,但快照里不能有这种「半吊子」状态
注意:不一致的快照比没有快照更可怕。没有快照,系统顶多重跑;不一致的快照,会导致恢复后的系统产生数据错误,而且这种错误往往很难排查。我曾经在线上见过因为不一致快照导致的数据重复计算,查了三天才定位到根因。
2.3.1 一致性快照的经典算法:Chandy-Lamport
说到一致性快照,就绕不开 Chandy-Lamport 算法。这是分布式系统领域的经典算法,由 K. Mani Chandy 和 Leslie Lamport 在 1985 年提出。很多现代系统的 Checkpoint 机制,底层思想都源于它。
算法的核心思路很简单:
- 发起者:选择一个节点作为协调者,它先记录自己的局部状态,然后向所有邻居发送一个特殊的「标记消息」(Marker)
- 传播:其他节点收到 Marker 后,如果还没记录自己的状态,就先记录当前状态,然后继续转发 Marker
- 记录通道:如果节点在记录状态后,又收到了其他节点发来的普通消息,这些消息就属于「通道中的在途消息」,需要记录下来
- 终止:当所有节点都记录完状态,所有通道都记录完在途消息,快照就完成了
这个算法的精妙之处在于:它不需要全局时钟,也不需要暂停所有节点。节点可以继续处理消息,只是多了一个「记录」的动作。
我的建议:如果你在实现自己的 Checkpoint 机制,Chandy-Lamport 算法是很好的起点。但要注意,它假设通道是 FIFO 的(先进先出)。如果你的消息通道不是 FIFO,需要做额外处理。我在一个基于 Kafka 的系统中就踩过这个坑——Kafka 分区内是 FIFO,但跨分区就不保证了。
2.4 知识体系总览
下面这张图,把这一章的核心概念串起来了。你可以把它当作 Checkpoint 的「思维导图」:
2.5 小结
这一章我们聊了三个核心概念:
- Checkpoint:就是给分布式系统拍「合影」,存到可靠存储里,用于崩溃恢复
- 局部状态 vs 全局状态:局部状态只管单个节点,全局状态要管所有节点和通道里的消息
- 一致性快照:快照必须对应一个真实存在的全局时间点,不能有「半吊子」状态
你可能会问:拍快照的时候,系统要不要停下来?如果不停,怎么保证一致性?嗯,这就是下一章要聊的——同步 Checkpoint 和异步 Checkpoint。不过那是后话了,先把这些概念消化掉。
避坑提醒:我在实际项目中见过不少团队,一上来就追求「高性能异步 Checkpoint」,结果忽略了最基础的一致性保证。我的建议是:先保证正确,再优化性能。一个跑得慢但正确的系统,比一个跑得快但偶尔算错数据的系统,要靠谱得多。