这是我参与「第四届青训营 」笔记创作活动的第3天 Exactly Once 语义在Flink中的实现
一. 数据流与动态表
1.1 传统SQL与流处理的区别
1.2 数据流与动态表的转换
1.2.1 在流上定义表:数据流 -> 动态表
- 动态表:与批处理数据的静态表不同,动态表是随时间变化的,但是对于查询,可以像对待静态表一样处理。
- 数据库表是INSERT、UPDATE、DELETE DML语句的stream的结果,通常称为 changelog stream。
1.2.2 连续查询:动态表 -> 动态表
连续查询:查询从不中止;查询结果会不断更新,产生一个新的动态表。
查询更新先前输出的结果,即定义结果表的changelog流包含UPDATE和INSERT
查询只附加到结果表,即结果表的changelog流只包含INSERT操作
1.2.3 Retract消息的产生:动态表 -> 流
- 使用flinkSQL处理实时数据当我们把表转化成流的时候,需要用
toAppendStream
与toRetractStream
这两个方法。- 追加模式:只有在动态
Table
仅通过INSERT
更改修改时才能使用此模式,即它仅附加,并且以前发出的结果永远不会更新。 - 始终可以使用此模式。返回值是
boolean
类型。它用true
或false
来标记数据的插入和撤回,返回true
代表数据插入,false
代表数据的撤回。
- 追加模式:只有在动态
- 按照官网的理解如果数据只是不断添加,可以使用追加模式,其余方式则不可以使用追加模式,而缩进模式侧可以适用于更新,删除等场景。具体的区别如下图所示:
没有retract会导致数据出错。
1.2.4 状态
需要存储每个用户的url数,以便能够增加该计数并在输入表接受新行时发送新结果。
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算法
2.1.1 快照制作的开始
每一个source
算子都接收到JM发送的Checkpoint Barrier
标识状态快照的开始制作
2.1.2 source算子的处理
各个source
算子保存自己的状态后(如保存到远程存储),向所有连接的下游继续发送Checkpoint Barrier
,同时告知JM自己状态已制作完成
2.1.3 Barrier Alignment
- 算子会等待其所有上游的barrier到达后才开始处理快照(接受到一个后就开始阻塞stream的处理,直到上游barrier全部到达并完成快照)
- 已经处理完的上游算子会继续处理数据,并不会被下游制作快照所影响
2.1.4 checkpoint结束
所有算子都告知JM状态制作完成后,整个checkpoint结束
三. Flink端到端的Exactly-Once语义
Question:虽然第二章的方法实现了各个算子的Exactly-Once,但是对于consumer来说,仍然会有着重复数据。如:sink将数据打印输出,t1
开始打印,t2
时进行了快照保存,t3
时发生failure,回复到t2
并重新计算,但此时t2
到t3
的信息又会被重复输出。
即:
- checkpoint能保证每条数据都对有状态的算子更新一次,sink输出算子仍可能下发重复的数据;
- 严格的端到端Exactly-Once语义还需要在checkpoint的基础上加上特殊的sink算子实现
3.1 两阶段提交协议(2PC)
个人理解:有点类似于数据库中的事务,分为预提交与提交两个阶段,只有所有协作者收到消息并成功执行告诉协作者后,协作者才允许参与者们正式提交。
由于2PC协议比较复杂,所以flink对它做了抽象,即TwoPhaseCommitSinkFunction。可以通过以下四步实现:
- beginTransaction。开始一次事务,在目的文件系统创建一个临时文件。接下来我们就可以将数据写到这个文件。
- preCommit。在这个阶段,将文件flush掉,同时重起一个文件写入,作为下一次事务的开始。
- commit。这个阶段,将文件写到真正的目的目录。值得注意的是,这会增加数据可视的延时。
- abort。如果回滚,那么删除临时文件。 如果pre-commit成功了但是commit没有到达算子旧宕机了,flink会将算子恢复到pre-commit时的状态,然后继续commit。我们需要做的还有就是保证commit的幂等性,这可以通过检查临时文件是否还在来实现。
3.2 Flink中的2PC Sink
个人理解:
变成了不断执行事务的过程,每次sink写数据前都开事务,在某一时间开始做checkpoint(此时sink也不处理数据了,后续的数据在下一个事务中处理),checkpoint结束后结束此次事务。