这是我参与「第四届青训营 」笔记创作活动的的第4天
[第四届青训营笔记创作活动]
一、不同数据处理保证的语义
- At-most-once:出现故障时,不进行处理。数据处理不保证任何语义,每条数据至多被处理一次,处理时延低。
- At-least-once:保证每条数据至少被处理一次,可能存在重复消费。
- Exactly-once:最严格的处理语义,每条数据均被消费且精确消费一次,仿佛故障从未发生。
二、分布式快照机制
Flink采用基于 checkpoint 的分布式快照机制,能够保证作业出现 fail-over 后可以从最新的快照进行恢复,即分布式快照机制可以保证 Flink 系统内部的“精确一次”处理。
Flink 分布式快照的核心元素:
- Barrier(数据栅栏) :可以把 Barrier 简单地理解成一个标记,该标记是严格有序的,并且随着数据流往下流动。每个 Barrier 都带有自己的 ID,Barrier 极其轻量,并不会干扰正常的数据处理。
- 异步:每次在把快照存储到我们的状态后端时,如果是同步进行就会阻塞正常任务,从而引入延迟。因此 Flink 在做快照存储时,采用异步方式
- 增量:由于 checkpoint 是一个全局状态,用户保存的状态可能非常大,多数达 G 或者 T 级别,checkpoint 的创建会非常慢,而且执行时占用的资源也比较多,因此 Flink 提出了增量快照的概念。也就是说,每次进行的全量 checkpoint,是基于上次进行更新的。
三、事务写入
实现思想:构建的事务对应着 checkpoint,等到 checkpoint 真正完成的时候,才把所有对应的结果写入 sink 系统中
实现方式:
- 预写日志(WAL):把结果数据先当成状态保存,然后在收到 checkpoint 完成的通知时,一次性写入 sink 系统。
缺点:做不到严格意义上的Exactly-once,写到一半时挂掉可能重复写入。 - 两阶段提交(2PC):
- 对于每个 checkpoint,sink 任务会启动一个事务,并将接下来所有接收的数据添加到事务里
- 然后将这些数据写入外部 sink 系统,但不提交它们,这时只是“预提交”
- 当它收到 checkpoint 完成的通知时,它才正式提交事务,实现结果的真正写入
- 这种方式真正实现了 Exactly-once,它需要一个特殊的提供事务支持的外部 sink 系统
三、End-to-End Exactly-once
-
内部保证 —— checkpoint
-
source 端 —— 支持数据重放
-
sink 端 —— 从故障恢复时,数据不会重复写入外部系统(幂等写入、事务写入)
四、Flink-Kafka Exactly-once
Flink 通过强大的异步快照机制和两阶段提交,实现了“端到端的精确一次语义”。
“端到端(End to End)的精确一次”,指的是 Flink 应用从 Source 端开始到 Sink 端结束,数据必须经过的起始点和结束点。Flink 自身是无法保证外部系统“精确一次”语义的,所以 Flink 若要实现所谓“端到端(End to End)的精确一次”的要求,那么外部系统必须支持“精确一次”语义;然后借助 Flink 提供的分布式快照和两阶段提交才能实现。
整个过程可以总结为下面四个阶段:
- 一旦 Flink 开始做 checkpoint 操作,那么就会进入 pre-commit 阶段,同时 Flink JobManager 的Coordinator会将检查点 Barrier 注入数据流中 ;
- 当所有的 barrier 在算子中成功进行一遍传递,并完成快照后,则 pre-commit 阶段完成;
- 等所有的算子完成“预提交”,就会发起一个commit“提交”动作,但是任何一个“预提交”失败都会导致 Flink 回滚到最近的 checkpoint;
- pre-commit 完成,必须要确保 commit 也要成功,上图中的 Data Sink 和 Kafka 会共同来保证。
五、Flink 两阶段提交总结
-
事务开启: 在sink task向下游写数据之前,均会开启一个事务,后续所有写数据的操作均在这个事务中执行,事务未提交前,事务写入的数据下游不可读;
-
预提交阶段:JM 开始下发 Checkpoint Barrier,当各个处理逻辑接收到 barrier 后停止处理后续数据,对当前状态制作快照,此时 sink 也不再当前事务下继续处理数据(处理后续的数据需要新打开下一个事务)。状态制作成功则向 JM 发送成功的消息,失败则发送失败的消息;数据已经写到 Kafka,但没有commit,下游不可读。
-
提交阶段:若JM收到所有预提交成功的消息,则向所有处理逻辑(包括 sink)发送可以提交此次事务的消息,sink接收到此消息后,则完成此次事物的提交,下一次恢复可以从这个点安全的从新写数据,此时下游可以读到这次事务写入的数据;若 JM 有收到预提交失败的消息,则通知所有处理逻辑回滚这次事物的操作,此时sink 搜到回滚时,将 Kafka 中所有的数据丢弃,下游永远不会读到,consumer 负责读 Kafka 的数据会直接跳过,读下一部分的数据。