Flink 容错机制
Alex90 2019.01.08 11:01:24
Flink 提供了容错机制,可以恢复数据流应用到一致状态。该机制确保在发生故障时,程序的状态最终将只反映数据流中的每个记录一次(exactly once)
,有一个开关可以降级为至少一次(at-least-once)。容错机制不断地创建分布式数据流的快照,对于小状态的流式程序,快照非常轻量,可以高频率创建而对性能影响很小。流式程序的状态存储在可配置的位置(如主节点或 HDFS 上)。当程序失败(由于机器、网络或软件故障),Flink 停止分布式数据流。然后系统重新启动 operator 并将其重置为最新成功的 checkpoint,输入流重置为相应的状态快照位置,保证被重启的并行数据流中处理的任何一个记录都不是 checkpoint 状态之前的一部分。
默认情况下,禁用检查点。
为了容错机制生效,数据源(例如 queue 或者 broker)需要能够回滚到指定位置重放数据流。Apache Kafka 有这个特性,Flink 中 Kafka 的 connector 利用了这个功能。
由于 Flink 的 checkpoint 是通过分布式快照实现的,接下来我们将 snapshot 和 checkpoint 这两个词交替使用。
由于 Flink checkpoint 是通过分布式 snapshot 实现的,因此我们在说法上 snapshot 和 checkpoint 可以互换使用。
Checkpoint
Flink 容错机制的核心就是持续地创建分布式数据流和操作算子状态的 snapshot。这些快照(snapshot)在程序出错失败时可以回退到一致性检查点,Flink 用于绘制这些 snapshots 的机制在 Lightweight Asynchronous Snapshots for Distributed Dataflows 中进行了描述。受分布式快照算法的标准 Chandy-Lamport 算法启发,并针对 Flink 执行模型量身定制。
Barriers
Flink 分布式快照的核心是 stream barriers。这些 barriers 被插入到数据流中,并作为数据流的一部分和记录一起向下游。Barriers 永远不会超过正常数据,数据流严格有序。一个 barrier 将数据流中的记录分割为进入当前快照的一组记录和进入下一个快照的记录。每个 barrier 都带有快照ID,并且 barrier 之前的记录都进入了此快照。Barriers 不会中断数据流,所以非常的轻量。多个不同快照的多个 barriers 可以同时在 stream 中出现,即多个快照可能同时创建。
stream barrier
Stream barriers 在 source stream 的并行数据流中插入。当 snapshot n 被插入(计作Sn),Sn点是 source stream 中 snapshot 覆盖数据的位置。例如在 Apache Kafka 中,此位置表示某个分区中最后一条数据的偏移量(offset)。Sn点被发送给 checkpoint coordinator(Flink JobManger)。
然后 barrier 继续移动。当中间算子从其所有的输入流(input stream)中收到 snapshot n 的 barrier 时,会向其所有输出流(outgoing stream)插入 snapshot n 的 barrier。一旦 Sink operator(流式DAG的末端)从其所有输入流中接受到 barrier n,向 checkpoint coordinator 确认 snapshot n 已完成,在所有 sinks 确认之后,该 snapshot 被认为已完成。
一旦 snapshot n 完成,作业将永远不会再向 source 请求Sn之前的记录,因为这些记录已经都走完了整个拓扑图。
stream aligning
接收超过一个输入流的 operator 需要基于 snapshot barrier 对齐(align)输入。参见上图:
- 当算子从输入流接收到 snapshot 的 barrier n,就不能继续处理此数据流的后续数据,知道其接收到其余流的 barrier n为止。否则会将属于 snapshot n 和 snapshot n+1的数据混淆
- 接收到 barrier n 的流的数据会被放在一个 input buffer 中,暂时不会处理
- 当从最后一个流中接收到 barrier n 时,算子会 emit 所有暂存在 buffer 中的数据,然后自己向下游发送 Snapshot n
- 最后算子恢复所有输入流数据的处理,优先处理输入缓存中的数据
State
当算子包含任何形式的 state 时,此状态(state)也必须是快照的一部分。算子状态有不同的形式:
- 用户定义的状态(User-defined state),由 transformation 函数(如map()或filter())直接创建或修改的状态。
- 系统状态(System state),作为算子计算一部分的缓存数据。典型例子就是窗口缓存(window buffers),系统在其中收集(聚合)窗口的记录,直到窗口被处理。
当算子接收到所有输入流中的 barriers 后,会对其状态进行快照,在提交 barriers 到输出流之前。这时,在 barrier 设置之前所有数据会对状态进行更新,并且之后不会再依赖 barrier 之前数据。由于 snapshote 的状态可能会很大,因此存储在可配置的 state backend 中。默认在 JobManager 的内存中,但是对于生产使用,应使用可靠的分布式存储系统(如HDFS)。在状态存储后,算子确认 checkpoint 完成,将 snapshot barrier 发送到输出流后恢复处理。
生成的 snapshot 包含:
- 对于每个并行输入数据源:快照创建时数据流中的位置偏移
- 对于每个 算子:存储在快照中的状态指针
checkpointing
Exactly Once vs. At Least Once
对齐操作可能会增加流处理的延迟,通常这种额外的延迟在毫秒级,但是我们也遇到过延迟显著增加的异常情况。对于要求所有记录处理都保持毫秒级低延迟的应用,Flink 提供了在 checkpoint 时跳过对齐的开关。一旦算子从每个输入流接收到检查点,就会开始绘制快照。
当跳过对齐时,算子会继续处理所有的输入,即使当 checkpoint n 的 barrier 到达时。也就是说,算子在 checkpoint n 创建之前,继续处理属于 checkpoint n+1 的数据。当恢复异常时,这部分记录就会重复处理,因为它们的处理状态已经被包含在了 checkpoint n 中,同时也会在之后再次被重放处理。
对齐操作只会发生在拥有多输入运算(join)或者多个输出(repartition、shuffle)的算子的场景下。所以,对于只有 map()、flatmap()、fliter() 等并行操作即使在至少一次的模式中仍然会保证严格一次。
Asynchronous State Snapshots
上述机制意味着当算子会停止处理输入记录,当算子在存储 snapshot 时。这种同步会在创建快照时引入延迟。
可以让算子在存储快照时继续处理数据,让快照存储异步在后台运行。因此,算子必须生成一个 state 对象以某种方式存储,保证后续状态的修改不会改变这个 state 对象。例如 RocksDB 中使用的 copy-on-write(写时复制)类型的数据结构。
在接受输入的到检查点 barriers,算子启动异步的 snapshot 复制其状态。会立刻向下游提交 barrier,然后继续正常数据的处理。当后台复制过程完成,会向 checkpoint coordinator(JobManager)进行确认。检查点完成的充分条件是:所有 sink 接收到了 barrier,所有有状态算子都确认完成了状态备份。
Recovery
在这种机制下的错误恢复:Flink 选择最近一次完成的检查点k,系统重新部署整个分布式数据流,并为每个算子恢复到检查点k的状态。输入源从Sk位置开始读取,例如在Kafka中,从Sk保存的偏移量开始读取。
如果是增量快照,算子需要从最新的圈梁快照恢复,然后对此状态进行一系列增量更新。
Operator Snapshot Implementation
算子创建快照由两部分操作:同步(synchronous)和异步(asynchronous)部分
算子和 state backend 将快照作为Java的 FutureTask。这个任务包含了已完成同步部分和pending的异步部分,异步的部分在后台线程中执行。完全同步的检查点的算子返回已完成的 FutureTask。如果需要执行异步算子操作,则以该 run() 方法执行 FutureTask。这些任务是可以取消的,来释放流和其他资源。