暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Flink 端到端 Exactly Once 语义实现原理

大数据记事本 2021-03-20
2482
一、什么是状态的一致性?
    Flink 是有状态的流计算框架,即每个算子都可以有自己的状态。对于流处理器内部来说,所谓状态的一致性,简而言之就是计算结果的正确性。比如要对流中的所有数据进行求和操作,当 1,3,5 三条数据到达后,计算的结果刚好就是9,不会重复计算某个数据,也不会丢失某个数据。当遇到故障时可以恢复状态,而恢复以后进行的重新计算,结果也应该是完全正确的。
二、状态一致性的分类
    At Most Once:至多一次。当故障发生时,最简单的做法就是什么都不做,既不用恢复丢失的状态,也不用重播丢失的数据,一条数据在整个流程中最多被计算一次,存在数据丢失的风险。
    At Least Once:至少一次。在很多场景下,都不希望丢失数据,而数据重复被计算却是允许的。该语义下,数据存在被重复计算的风险。
    Exactly Once:精确一次。每条数据刚好被计算一次,不丢失也不重复。
    End To End Exactly Once:端到端的精确一次。

以上四种分类,一致性由弱到强,同时实现难度也由易到难。

Exactly Once 和 End To End Exactly Once 的区别:
  • Exactly Once:保证所有数据只影响内部状态一次
  • End To End Exactly Once:保证所有数据对内部和外部状态都只影响一次。
三、Flink 状态一致性的实现原理

    对于 Flink 内部的一致性保证,依靠一致性检查点 checkpoint 来实现,通过 barrier 是否对齐来决定是 At Least Once 还是 Exactly Once(关于 checkpoint 可以查看《Flink | Checkpoint 机制详解》)。

    而对于端到端的 Exactly Once 语义,实现原理如下:

  • Source端:可重设数据的读取位置。即source端会将数据的读取位置作为状态进行存储,当发生故障恢复时,依靠记录的读取位置,重新从数据源读取数据进行计算。
  • Flink内部:checkpoint机制
  • Sink端:要求从故障恢复时,数据不会重复地写入外部系统。具体实现方式有两种:
  • 幂等写入:即多次写入的结果是一样的。这种一般针对NoSQL数据库,如 redis、HBase等,只要key-value一样,多次写入的结果是一样的。(注意这里只是结果一致,但不保证过程也是一致的
  • 事务写入:即事务中的操作要么全部成功,要么全部失败。实现思想是构建的事务对应 Flink 中的 checkpoint,只有等到 checkpoint 真正完成的时候,才把对应的结果写入到 sink 系统中(或者是执行真正的提交)。

事务写入的实现方式:两阶段提交(2PC)

    两阶段提交 sink 即TwoPhaseCommitSinkFunction,是 Flink 从 1.4 版本开始引入的。

    该 SinkFunction 提取并封装了两阶段提交协议中的公共逻辑,从此 Flink 可以搭配特定 Source 和 Sink 搭建精确一次处理语义( exactly-once semantics)。作为一个抽象类 TwoPhaseCommitSinkFunction 提供了一个抽象层供用户自行实现特定方法来支持 Exactly-Once 语义。

两阶段提交的过程:
    对于每个 checkpoint,sink 任务会启动一个事务,并将接下来所有接收的数据添加到该事务中。
    然后将这些数据写入外部 sink 系统,但不提交他们——这里的写入只是 "预提交"。   
 这里虽然已经写入了外部 sink 系统,但由于没有正式提交,对其他组件是不可见的。
    当它收到 checkpoint 完成的通知时,它才正式提交事务,实现结果的真正写入,即写入 sink 系统的数据对其他组件可见。
两阶段提交对外部 sink 系统的要求:
  • 外部 sink 系统必须提供事务支持,或者 sink 任务必须能够模拟外部系统上的事务
  • 在 checkpoint 的间隔期间里,必须能够开启一个事务并接受数据的写入
  • 在收到 checkpoint 完成的通知之前,事务必须是预提交的状态。
四、Kafka+Flink+Kafka 实现端到端 Exactly Once
前提:kafka 版本在 0.11 及以上,因为 kafka 从 0.11 版本开始支持事务
    1.JobManager 启动 checkpoint 并向Source发送Barrier,开始进入pre-Commit阶段。当Source 收到 Barrier后,将自身的状态保存到StateBackend 状态后端,这里的状态是指消费的每个分区对应的 offset。然后 Source 将Barrier发送给下一个Operator。

    2.当 Window 这个 Operator 收到 Barrier 之后,对自己的状态进行保存,然后将Barrier 发送给 Sink。Sink收到后也对自己的状态进行保存,之后会进行一次预提交,并向 JobManager 报告预提交情况。

    3.预提交成功后,JobManager 会通知每个Operator,这一轮 checkpoint 已经完成,这个时候,Kafka Sink会向 Kafka 进行真正的事务 Commit。

4.提交过程中如果失败有以下两种情况

  • Pre-commit失败,将会恢复到上一次完成 checkpoint 的状态

  • pre-commit完成,但正式 commit 过程中崩溃。那么当任务恢复后,Flink 会自动完成正式 commit

文章转载自大数据记事本,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论