Flink 通过强大的异步快照机制和两阶段提交,实现了“端到端的精确一次语义”。
“端到端(End to End)的精确一次”,指的是 Flink 应用从 Source 端开始到 Sink 端结束,数据必须经过的起始点和结束点。Flink 自身是无法保证外部系统“精确一次”语义的,所以 Flink 若要实现所谓“端到端(End to End)的精确一次”的要求,那么外部系统必须支持“精确一次”语义;然后借助 Flink 提供的分布式快照和两阶段提交才能实现。
一、分布式快照机制
flink采用基于 checkpoint 的分布式快照机制,能够保证作业出现 fail-over 后可以从最新的快照进行恢复,即分布式快照机制可以保证 Flink 系统内部的“精确一次”处理。
Flink 分布式快照的核心元素
- Barrier(数据栅栏):可以把 Barrier 简单地理解成一个标记,该标记是严格有序的,并且随着数据流往下流动。每个 Barrier 都带有自己的 ID,Barrier 极其轻量,并不会干扰正常的数据处理。
- 异步:每次在把快照存储到我们的状态后端时,如果是同步进行就会阻塞正常任务,从而引入延迟。因此 Flink 在做快照存储时,采用异步方式
- 增量:由于 checkpoint 是一个全局状态,用户保存的状态可能非常大,多数达 G 或者 T 级别,checkpoint 的创建会非常慢,而且执行时占用的资源也比较多,因此 Flink 提出了增量快照的概念。也就是说,每次进行的全量 checkpoint,是基于上次进行更新的。
二、两阶段提交
Flink 中两阶段提交的实现方法被封装到了 TwoPhaseCommitSinkFunction 这个抽象类中,我们只需要实现其中的beginTransaction、preCommit、commit、abort 四个方法就可以实现“精确一次”的处理语义。
- beginTransaction,在开启事务之前,创建一个临时文件夹,然后把数据写入这个文件夹里面
- preCommit,在预提交阶段,将内存中缓存的数据写入文件并关闭;
- commit,在提交阶段,将预提交写入的临时文件放入目标目录下,这代表着最终的数据会有一些延迟;
- abort,在中止阶段,我们删除临时文件。
三、Flink-Kafka Exactly-once
整个过程可以总结为下面四个阶段:
- 一旦 Flink 开始做 checkpoint 操作,那么就会进入 pre-commit 阶段,同时 Flink JobManager 会将检查点 Barrier 注入数据流中 ;
- 当所有的 barrier 在算子中成功进行一遍传递,并完成快照后,则 pre-commit 阶段完成;
- 等所有的算子完成“预提交”,就会发起一个“提交”动作,但是任何一个“预提交”失败都会导致 Flink 回滚到最近的 checkpoint;
- pre-commit 完成,必须要确保 commit 也要成功,上图中的 Sink Operators 和 Kafka Sink 会共同来保证。