四、错误恢复
在 Flink 作业的执行过程中,除正常执行的流程外,还有可能由于环境等原因导致各种类型的错误。整体上来说,错误可能分为两大类:Task 执行出现错误或 Flink 集群的 Master 出现错误。由于错误不可避免,为了提高可用性,Flink 需要提供自动错误恢复机制来进行重试。
对于第一类 Task 执行错误,Flink 提供了多种不同的错误恢复策略。如图 8 所示,第一种策略是 Restart-all,即直接重启所有的 Task。对于 Flink 的流任务,由于 Flink 提供了 Checkpoint 机制,因此当任务重启后可以直接从上次的 Checkpoint 开始继续执行。因此这种方式更适合于流作业。第二类错误恢复策略是 Restart-individual,它只适用于 Task 之间没有数据传输的情况。这种情况下,我们可以直接重启出错的任务。
图8.Restart-all 错误恢复策略示例。
该策略会直接重启所有的 Task。
图9.Restart-individual 错误恢复策略示例。
该策略只适用于 Task之间不需要数据传输的作业,对于这种作业可以只重启出现错误的 Task。
由于 Flink 的批作业没有 Checkpoint 机制,因此对于需要数据传输的作业,直接重启所有 Task 会导致作业从头计算,从而导致一定的性能问题。为了增强对 Batch 作业,Flink 在1.9中引入了一种新的Region-Based的Failover策略。在一个 Flink 的 Batch 作业中 Task 之间存在两种数据传输方式,一种是 Pipeline 类型的方式,这种方式上下游 Task 之间直接通过网络传输数据,因此需要上下游同时运行;另外一种是 Blocking 类型的试,如上节所述,这种方式下,上游的 Task 会首先将数据进行缓存,因此上下游的 Task 可以单独执行。基于这两种类型的传输,Flink 将 ExecutionGraph 中使用 Pipeline 方式传输数据的 Task 的子图叫做 Region,从而将整个 ExecutionGraph 划分为多个子图。可以看出,Region 内的 Task 必须同时重启,而不同 Region 的 Task 由于在 Region 边界存在 Blocking 的边,因此,可以单独重启下游 Region 中的 Task。
基于这一思路,如果某个 Region 中的某个 Task 执行出现错误,可以分两种情况进行考虑。如图 8 所示,如果是由于 Task 本身的问题发生错误,那么可以只重启该 Task 所属的 Region 中的 Task,这些 Task 重启之后,可以直接拉取上游 Region 缓存的输出结果继续进行计算。
另一方面,如图如果错误是由于读取上游结果出现问题,如网络连接中断、缓存上游输出数据的 TaskExecutor 异常退出等,那么还需要重启上游 Region 来重新产生相应的数据。在这种情况下,如果上游 Region 输出的数据分发方式不是确定性的(如 KeyBy、Broadcast 是确定性的分发方式,而 Rebalance、Random 则不是,因为每次执行会产生不同的分发结果),为保证结果正确性,还需要同时重启上游 Region 所有的下游 Region。
图10.Region-based 错误恢复策略示例一。
如果是由于下游任务本身导致的错误,可以只重启下游对应的 Region。
图11.Region-based 错误恢复策略示例二。
如果是由于上游失败导致的错误,那么需要同时重启上游的 Region 和下游的 Region。实际上,如果下游的输出使用了非确定的数据分割方式,为了保持数据一致性,还需要同时重启所有上游 Region 的下游 Region。
除了 Task 本身执行的异常外,另一类异常是 Flink 集群的 Master 进行发生异常。目前 Flink 支持启动多个 Master 作为备份,这些 Master 可以通过 ZK 来进行选主,从而保证某一时刻只有一个 Master 在运行。当前活路的 Master 发生异常时,某个备份的 Master 可以接管协调的工作。为了保证 Master 可以准确维护作业的状态,Flink 目前采用了一种最简单的实现方式,即直接重启整个作业。实际上,由于作业本身可能仍在正常运行,因此这种方式存在一定的改进空间。
五、未来展望
Flink目前仍然在Runtime部分进行不断的迭代和更新。目前来看,Flink未来可能会在以下几个方式继续进行优化和扩展:
- 更完善的资源管理:从 1.9 开始 Flink 开始了对细粒度资源匹配的支持。基于细粒度的资源匹配,用户可以为 TaskExecutor 和 Task 设置实际提供和使用的 CPU、内存等资源的数量,Flink 可以按照资源的使用情况进行调度。这一机制允许用户更大范围的控制作业的调度,从而为进一步提高资源利用率提供了基础。
- 统一的 Stream 与 Batch:Flink 目前为流和批分别提供了 DataStream 和 DataSet 两套接口,在一些场景下会导致重复实现逻辑的问题。未来 Flink 会将流和批的接口都统一到 DataStream 之上。
- 更灵活的调度策略:Flink 从 1.9 开始引入调度插件的支持,从而允许用户来扩展实现自己的调度逻辑。未来 Flink 也会提供更高性能的调度策略的实现。
- Master Failover 的优化:如上节所述,目前 Flink 在 Master Failover 时需要重启整个作业,而实际上重启作业并不是必须的逻辑。Flink 未来会对 Master failover 进行进一步的优化来避免不必要的作业重启。