Flink的Exactly Once语义到底是什么意思?和去重有没有关系?

今天更新一个提高班同学面试中的问题。这个问题是关于Flink的Exactly Once语义的。

这个问题的背景是,之前大数据提高班的同学在实际工作中在精确一次场景中错误的理解了Flink的Exactly-Once语义,一直使用至今。在面试中被面试官抓住这个问题一顿毒打,最终面试失败告终。

今天就是简单回答一下这个问题。

首先,我们要明确问题,这个问题其实是两个小问题,第一是,Flink中的精确一次是什么意思?第二是,精确一次计算到底该怎么实现?

我们先抛出结论,在生产环境中,单纯利用Flink自身的Exactly-once语义去实现数据的精确一次计算很难实现。尤其是要求极其严格的场景,例如商家的订单量统计。

为什么会这样?

大家都知道,在 Flink 1.4 版本正式引入了一个里程碑式的功能:两阶段提交 Sink,即 TwoPhaseCommitSinkFunction 函数。该 SinkFunction 提取并封装了两阶段提交协议中的公共逻辑,自此 Flink 搭配特定 Source 和 Sink(如 Kafka 0.11 版)实现精确一次处理语义(英文简称:EOS,即 Exactly-Once Semantics)成为可能。

这个功能是不是大家理解的所谓「流中的每个事件只被处理一次」呢?答案是否定的,事实上没有一个引擎能做到「只处理一次」。面对故障时,不可能保证每个算子中的用户定义逻辑在每个事件中只执行一次,而对同一事件的处理可以发生很多次。

那Flink声明的「Exactly-Once」处理语义,它到底保证的是什么?实际上它说的是,引擎管理的状态更新只提交一次到状态中。如果在配合上source和sink对「Exactly-Once」语义的支持,就是大家所谓的「端到端Exactly-Once」。

此外,我们在Flink的官网中可以找到一段话:

Flink的Exactly Once语义到底是什么意思?和去重有没有关系?_数据

这段话的意思是说,Flink不依赖提交到Kafka的offset进行容错管理,只是用来监控或者展示。如果开启Flink的Checkpointing机制,默认的offset提交策略是:Flink Kafka消费者会在每次设置Checkpoint完成后提交Kafka的offset。可以通过setCommitOffsetsOnCheckpoints(false)取消提交Offset与设置检查点之间的关联。

所以,我们可以看到Flink本身的Exactly-once语义是借助Checkpoint实现的。

再回过头来说「精确一次计算」。我们要实现数据的精确一次计算,就要保证source和sink不受数据重复的影响。

一般来说需要三重保障:

  1. 借助第三方存储实现数据的精确一次下发,不能有重复;
  2. Flink代码本身要有去重,例如排序和distinct逻辑;并且必须开启CheckPoint进行位点管理;
  3. Sink保证幂等

有了以上三重保障,那么这时候Flink的CheckPoint Mode是不是Exactly Once已经不重要了。

所以本质上这是2个问题,不能混为一谈。