而 Flink 应该让 Sink 开发者提供 What to commit 和 How to commit,而系统应该根据不同的执行模式,选择 Where to commit 和 When to commit 来保证端到端的 Exactly Once。最终我们提出了一个全新 Unified Sink API,从而让开发者只开发一套 Sink 就可以同时运行在 Streaming 和 Batch 执行模式下。这里介绍的只是主要思路,在有限流的场景下如何保证 End to End 的一致性;如何对接 Hive、Iceberg 等外部生态,实际上还是存在一定挑战。
架构不一致、维护成本高。调度的本质就是进行资源的分配,换句话说就是要解决 When to deploy which tasks to where 的问题。原有两种调度模式,在资源分配的时机和粒度上都有一定的差异,最终导致了调度架构上无法完全统一,需要开发人员维护两套逻辑。例如,流的调度模式,资源分配的粒度是整个物理执行计划的全部 Task;批的调度模式,资源分配的粒度是单个任务,当 Scheduler 拿到一个资源的时候,就需要根据作业类型走两套不同的处理逻辑;
JM 是一个作业的控制中心,包含了作业的各种执行状态。Flink 利用这些状态对任务进行调度和部署。一旦 JM 发生错误之后,这些状态将会全部丢失。如果没有这些信息,即便所有的工作节点都没有发生故障,新 JM 仍然无法继续调度原来的作业。例如,由于任务的结束信息都已丢失,一个任务结束之后,新 JM 无法判断现有的状态是否满足调度下游任务的条件——所有的输入数据都已经产生。 从上边的分析可以看出,JM Failover 的关键就是如何让一个 JM“恢复记忆”。在 VVR[12] 中我们通过基于 Operation Log 机制恢复 JM 的关键状态。 细心的同学可能已经发现了,虽然这两个改进的出发点是为了批的场景,但是实际上对于流的作业容也同样有效。上边只是简要的介绍了两种容错策略的思路,实际上还有很多值得思考的内容。例如 Blocking 上游数据丢失了我们应该如何处理?JM 中有哪些关键的状态需要恢复?