分布式系统没有足够可靠的全局时钟来同时协调数百个操作符的一致快照。一种简单的容错方法可能涉及“停止”机制,即所有处理暂停,状态写入磁盘,然后处理恢复。在高吞吐量流式环境中,这会引入不可接受的延迟峰值,并大幅降低性能。Apache Flink 通过使用异步屏障快照(ABS)来应对这一问题。该机制使得系统能够在不中断持续数据处理的情况下,创建全局状态的一致镜像。ABS 是 Chandy-Lamport 分布式快照算法的一种变体,专门为流作业的 directed acyclic graph (DAG) 拓扑结构进行了调整。检查点屏障的作用ABS 算法的核心组成部分是检查点屏障。与标准数据记录不同,屏障是由源操作符注入到数据流中的控制消息。这些屏障将无界流分成逻辑上的时期。所有在屏障 $n$ 之前的数据记录属于快照 $n$,而屏障之后的数据记录属于快照 $n+1$。屏障与数据记录一同向下游流动,严格保持每个通道内事件的顺序(先进先出)。由于屏障在流中占据特定位置,它们会在所有与屏障前数据相关的修改完成后,精确地触发状态持久化。检查点屏障通过简单操作符拓扑的传播图。屏障将属于当前检查点时期的数据与下一时期的数据分开。digraph G { rankdir=TB; node [shape=box, style=filled, fontname="Helvetica", color="#dee2e6", fillcolor="#f8f9fa"]; edge [fontname="Helvetica", fontsize=10]; subgraph cluster_0 { label = "源操作符"; style=filled; color="#e9ecef"; node [fillcolor="#ffffff"]; src [label="源"]; } subgraph cluster_1 { label = "转换"; style=filled; color="#e9ecef"; node [fillcolor="#ffffff"]; op1 [label="Map 函数"]; op2 [label="窗口操作符"]; } subgraph cluster_2 { label = "目标"; style=filled; color="#e9ecef"; node [fillcolor="#ffffff"]; sink [label="目标"]; } src -> op1 [label="流数据", color="#4dabf7"]; op1 -> op2 [label="屏障 N", color="#fa5252", style=dashed, penwidth=2]; op2 -> sink [label="已处理数据", color="#4dabf7"]; node [shape=plaintext, fillcolor=none]; legend [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"><TR><TD BGCOLOR="#fa5252">屏障</TD><TD BGCOLOR="#4dabf7">数据流</TD></TR></TABLE>>]; }屏障对齐和精确一次语义当一个操作符从多个输入通道接收数据时(例如,CoProcessFunction 或 KeyedWindow),它必须确保快照反映所有输入的一致视图。这需要一个被称为屏障对齐的过程。对于带有输入 $I_1$ 和 $I_2$ 的操作符,对齐过程如下进行:到达: 操作符从输入通道 $I_1$ 收到屏障 $n$。阻塞: 操作符停止处理来自 $I_1$ 的数据。在此通道上,它不能处理任何来自时期 $n+1$ 的数据,直到完成快照,否则状态将混合来自两个不同时期的数据。缓冲: 当 $I_1$ 被阻塞时,操作符继续处理来自输入 $I_2$ 的数据。完成: 一旦屏障 $n$ 从 $I_2$ 到达,两个输入就对齐了。快照: 操作符将屏障发送到其下游输出,并触发其本地状态的异步快照。恢复: 操作符解除 $I_1$ 的阻塞,并恢复处理两个通道中时期 $n+1$ 的数据。对齐阶段确保了精确一次的处理语义。如果系统发生故障并从该快照恢复,屏障前的每条记录都将被精确处理一次,屏障后的记录也不会被过早处理。数学上,设 $R_{i}$ 为通道 $i$ 上接收到的记录序列。检查点 $C_n$ 处的状态 $S$ 满足:$$ S(C_n) = f({r \in R_{i} \mid \text{索引}(r) < \text{索引}(\text{屏障}_n) \forall i}) $$其中 $f$ 表示状态转换函数。这意味着状态严格反映了流前缀直到屏障的数据消耗情况。权衡:延迟和反压屏障对齐引入了延迟方面的权衡。在对齐阶段,操作符会暂停处理较快的数据流(例如前述的 $I_1$),以等待较慢的数据流($I_2$)。在数据倾斜严重或网络抖动明显的拓扑中,这种等待时间会表现为反压,并向上游传播。如果对齐时间为 $t_{align}$,那么被阻塞通道中记录的总端到端延迟至少增加 $t_{align}$。对于需要超低延迟且恢复时偶尔重复处理可接受的应用,Flink 允许配置非对齐检查点。在此模式下,屏障会超越缓冲区中的数据记录,并且传输中的数据将作为检查点状态的一部分进行持久化,从而绕过对齐延迟,但代价是检查点大小会增加。异步状态快照屏障对齐后(或对于单输入操作符,屏障到达后立即),操作符执行状态快照。为避免阻塞主处理线程,Flink 将快照过程分为两个阶段:同步阶段: 操作符执行状态的轻量级本地复制。对于基于堆的后端,这可能是哈希表上的写时复制操作。对于 RocksDB,这涉及强制将 MemTable 刷写到磁盘并创建 SSTables 的硬链接。此阶段会停止处理,但通常在几毫秒内完成。异步阶段: 一个后台线程将状态文件传输到持久化远程存储(例如 S3、HDFS 或 GCS)。主流处理线程在同步阶段完成后立即恢复。这种分离确保了通过网络传输大量状态集的开销不会影响流作业的吞吐量。状态大小随时间相对于屏障间隔增长的图示。高效的快照操作将“停止时间”(同步阶段)降至最低。{"layout": {"title": {"text": "快照时序:同步与异步阶段", "font": {"family": "Helvetica", "size": 16, "color": "#495057"}}, "xaxis": {"title": {"text": "时间 (毫秒)", "font": {"family": "Helvetica", "color": "#868e96"}}, "showgrid": true, "gridcolor": "#dee2e6"}, "yaxis": {"title": {"text": "操作", "font": {"family": "Helvetica", "color": "#868e96"}}, "showgrid": false, "zeroline": false, "showticklabels": false}, "plot_bgcolor": "#ffffff", "paper_bgcolor": "#ffffff", "margin": {"l": 50, "r": 50, "t": 50, "b": 50}, "height": 300, "shapes": [{"type": "rect", "x0": 100, "y0": 0, "x1": 150, "y1": 1, "fillcolor": "#fa5252", "line": {"width": 0}, "opacity": 0.7}, {"type": "rect", "x0": 150, "y0": 0, "x1": 500, "y1": 1, "fillcolor": "#228be6", "line": {"width": 0}, "opacity": 0.7}], "annotations": [{"x": 125, "y": 0.5, "text": "同步", "font": {"color": "white", "family": "Helvetica"}, "showarrow": false}, {"x": 325, "y": 0.5, "text": "异步上传 (后台)", "font": {"color": "white", "family": "Helvetica"}, "showarrow": false}]}, "data": []}分布式协调运行在 JobManager 上的检查点协调器,管理整个生命周期。它触发源操作符注入屏障并收集所有任务的确认信息。只有当 DAG 中的所有操作符都成功确认了其针对特定屏障 ID 的状态句柄时,检查点才被视为完成。如果操作符未能确认快照(由于网络故障或磁盘 I/O 错误),协调器会将检查点 $n$ 标记为失败并清理任何部分数据。流处理继续不中断,依赖于下一个成功的检查点间隔来确定有效的恢复点。这种乐观策略确保了短暂的基础设施故障不会破坏实时数据管道,前提是成功检查点之间的间隔保持在服务级别协议(SLA)定义的数据丢失恢复限制内。