flink保证Exactly Once的方式、原理

1,025 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

满足以下三点,可以保证端到端的Exactly Once

  1. 开启checkpoint

  2. source支持数据重发

  3. sink端幂等性写入、事务性写入。我们常使用事务性写入

分布式快照机制(checkpoint)

flink采用基于 checkpoint 的分布式快照机制,保证作业出现 fail-over 后从最新的快照进行恢复,来保证 Flink 系统内部的Exactly Once处理。

核心元素

1、Barrier(数据栅栏): Barrier 可以简单地理解成一个标记,有序的随着数据流往下流动。每个Barrier 都带有自己的ID,它非常轻量,不会干扰正常的数据处理。

2、异步: 如果是同步把快照存储到我们的状态后端时,会阻塞正常任务,会引起延迟。因此 Flink 在做快照存储时,采用异步方式

3、增量:由于 checkpoint 是一个全局状态,用户保存的状态可能非常大,多数达 G 或者 T 级别,创建慢、占用资源多,为解决这个问题Flink 提出了增量快照的概念。也就是说,每次进行的全量 checkpoint,是基于上次进行更新的。

sink 事务性写入两种方式

1、WAL(预写日志的方式):先将数据当作状态保存,当收到checkpoint完成通知后,一次性sink到下游系统

2、2pc(两阶段提交):大致的实现的过程就是:

  • 开始事务(beginTransaction)创建一个临时文件夹,来写把数据写入到这个文件夹里面。
  • 预提交(preCommit)将内存中缓存的数据写入文件并关闭。
  • 正式提交(commit)将之前写完的临时文件放入目标目录下。这代表着最终的数据会有一些延迟。
  • 丢弃(abort)丢弃临时文件

    若失败发生在预提交成功后,正式提交前。可以根据状态来提交预提交的数据,也可删除预提交的数据。

flink-kafka的例子

一个典型例子:

data source从kafka消费数据,然后window聚合data,最后将处理后的数据sink到kafka

data sink为了提供exactly-once保证,必须将一个事务中的数据都写入到kafka,一次commit包含了2个checkpoint之间的所有的写操作,这保证了当失败时,也会回滚所有的写操作。

01.png

第一步:pre-commit阶段。

pre-commit是一次checkpoint的开始,Flink JobManager的Coordinator会将Barrier注入数据流中,flink在operator中传递,当一个operator接收到barrier,触发state snapshot。

比如:Kafka source会保存消费的offset,完成后传递barrier。这个过程只涉及internal state(internal state是由flink保存和管理的),是没有问题的,但是如果涉及到external state,则需要外部系统提供一致性保证,外部系统必须要提供对2PC的事务支持。

02.png

当所有的operator完成了checkpoint,Pre-commit阶段就算完成了。Checkpoint的snapshot包含了整个application的状态,包括外部系统的pre-commited的external state,如果发生失败,可以回滚到最近一次成功的snapshot。

03.png

JobManager通知所有的operator,checkpoint完成了,执行commit阶段。 下图中的data source和window operator没有external state,在commit执行阶段无需额外的操作。data sink有external state,需要commit这次事务。

04.png

整个流程如下:

  1. 当所有的operator完成了pre-commit(checkpoint snapshot),开启一个commit。
  2. 如果有一个pre-commit失败了,其他都abort,回滚到最近一次成功的checkpoint。
  3. Pre-commit成功后,所有的operator和外部系统必须保证commit执行成功,如果有失败(如网络中断), 整个flink application fail,flink任务按重启策略重启,开始一次新的commit尝试。