Exactly Once 语义在Flink中的实现 | 青训营笔记

63 阅读5分钟

这是我参与「第四届青训营 」笔记创作活动的第3天 Exactly Once 语义在Flink中的实现

一. 数据流与动态表

1.1 传统SQL与流处理的区别

image.png

1.2 数据流与动态表的转换

image.png

1.2.1 在流上定义表:数据流 -> 动态表

image.png

  • 动态表:与批处理数据的静态表不同,动态表是随时间变化的,但是对于查询,可以像对待静态表一样处理。
    • 数据库表是INSERT、UPDATE、DELETE DML语句的stream的结果,通常称为 changelog stream。

1.2.2 连续查询:动态表 -> 动态表

连续查询:查询从不中止;查询结果会不断更新,产生一个新的动态表。

查询更新先前输出的结果,即定义结果表的changelog流包含UPDATE和INSERT image.png

查询只附加到结果表,即结果表的changelog流只包含INSERT操作 image.png

1.2.3 Retract消息的产生:动态表 -> 流

image.png

  • 使用flinkSQL处理实时数据当我们把表转化成流的时候,需要用toAppendStreamtoRetractStream这两个方法。
    • 追加模式:只有在动态Table仅通过INSERT更改修改时才能使用此模式,即它仅附加,并且以前发出的结果永远不会更新。
    • 始终可以使用此模式。返回值是boolean类型。它用truefalse来标记数据的插入和撤回,返回true代表数据插入,false代表数据的撤回。
  • 按照官网的理解如果数据只是不断添加,可以使用追加模式,其余方式则不可以使用追加模式,而缩进模式侧可以适用于更新,删除等场景。具体的区别如下图所示: image.png 没有retract会导致数据出错。 image.png

1.2.4 状态

需要存储每个用户的url数,以便能够增加该计数并在输入表接受新行时发送新结果。 image.png

1.2.5 三种数据处理保证语义

  • at most once: 至多一次,表示一条消息不管后续处理成功与否只会被消费处理一次,存在数据丢失可能
  • at least once: 至少一次,表示一条数据最少被处理一次,可能会存在重复消费
  • exactly once: 精确一次,表示一条消息算子对下游产出的结果有且仅有一份会被下游获取

二. Exactly Once的实现:CheckPoint

全面讲解Flink中CheckPoint机制和Exactly Once / At Least Once应用

Flink CheckPoint 的存在就是为了解决 Flink 任务 failover 掉之后,能够正常恢复任务。 CheckPoint 是通过给程序 快照 的方式使得将历史某些时刻的状态保存下来,当任务挂掉之后,默认从最近一次保存的完整快照处进行恢复任务

2.1 Chandy-Lamport算法

image.png

2.1.1 快照制作的开始

每一个source算子都接收到JM发送的Checkpoint Barrier标识状态快照的开始制作 image.png

2.1.2 source算子的处理

各个source算子保存自己的状态后(如保存到远程存储),向所有连接的下游继续发送Checkpoint Barrier,同时告知JM自己状态已制作完成 image.png

2.1.3 Barrier Alignment

  • 算子会等待其所有上游的barrier到达后才开始处理快照(接受到一个后就开始阻塞stream的处理,直到上游barrier全部到达并完成快照)
  • 已经处理完的上游算子会继续处理数据,并不会被下游制作快照所影响 image.png

2.1.4 checkpoint结束

所有算子都告知JM状态制作完成后,整个checkpoint结束 image.png

三. Flink端到端的Exactly-Once语义

# Flink端到端Exactly-once

Question:虽然第二章的方法实现了各个算子的Exactly-Once,但是对于consumer来说,仍然会有着重复数据。如:sink将数据打印输出,t1开始打印,t2时进行了快照保存,t3时发生failure,回复到t2并重新计算,但此时t2t3的信息又会被重复输出。

即:

  • checkpoint能保证每条数据都对有状态的算子更新一次,sink输出算子仍可能下发重复的数据;
  • 严格的端到端Exactly-Once语义还需要在checkpoint的基础上加上特殊的sink算子实现

3.1 两阶段提交协议(2PC)

image.png 个人理解:有点类似于数据库中的事务,分为预提交提交两个阶段,只有所有协作者收到消息并成功执行告诉协作者后,协作者才允许参与者们正式提交。

由于2PC协议比较复杂,所以flink对它做了抽象,即TwoPhaseCommitSinkFunction。可以通过以下四步实现:

  • beginTransaction。开始一次事务,在目的文件系统创建一个临时文件。接下来我们就可以将数据写到这个文件。
  • preCommit。在这个阶段,将文件flush掉,同时重起一个文件写入,作为下一次事务的开始。
  • commit。这个阶段,将文件写到真正的目的目录。值得注意的是,这会增加数据可视的延时。
  • abort。如果回滚,那么删除临时文件。 如果pre-commit成功了但是commit没有到达算子旧宕机了,flink会将算子恢复到pre-commit时的状态,然后继续commit。我们需要做的还有就是保证commit的幂等性,这可以通过检查临时文件是否还在来实现。

image.png

3.2 Flink中的2PC Sink

image.png

image.png

image.png

image.png

image.png 个人理解: 变成了不断执行事务的过程,每次sink写数据前都开事务,在某一时间开始做checkpoint(此时sink也不处理数据了,后续的数据在下一个事务中处理),checkpoint结束后结束此次事务。 image.png