Flink 如何通过2PC实现Exactly-once语义 (源码分析)
Flink通过全局快照能保证内部处理的Exactly-once语义
但是端到端的Exactly-once还需要下游数据源配合,常见的通过幂等或者二阶段提交这两种方式保证
这里就来分析一下Sink二阶段提交的Flink源码是如何实现的
本文源码基于Flink1.14
老版本的话看TwoPhaseCommitSinkFunction,现在用SinkWriter逻辑都是差不多的
先来看下我们的主角 org.apache.flink.streaming.runtime.operators.sink.SinkOperator 类
1阶段. 在barrier到齐准备触发checkpoint之前
调用了数据源的预提交方法 prepareCommit
来看下已kafka为例具体做了什么
kafkaWriter就是调用了生产者的flush方法,在已经开始的事务里面刷数据
2阶段. 触发checkpoint保存状态数据的时候 snapshotState 方法
以kafka为例
会启动下一个checkpoint的kafka事务,直接就begin事务了,接着
用这次checkpoint需要commit的kafkaCommiter更新了状态, 会被保存下来,这里有事务信息的后面会用到
3阶段. 当checkpoint完成以后
已kafka为例,会直接提交事务了commit
这里可能会有疑问,,如果我只预提交了,还没有commit这时候跪了,那我从checkpoint恢复起来,那不就有问题了吗
带着疑问看下最后一个阶段
4阶段. 当任务失败从checkpoint恢复的时候
初始化的时候会恢复状态
可以看到会将上面说的上次checkpoint需要commiter的放到recoveredCommittables恢复队列里面
然后retrayWithDelay,就会根据我们保存的kafka事务信息id等去判断,上一次事务的状态,如果是预提交的话,就会先去commit了
总结一下流程:
prepareSnapshotPreBarrier快照触发前, 预提交事务,kafka里面就是flash
snapshotState快照保存时,开启一个新的事务kafka就是beginTransation,并且保存这次要提交的事务信息
notifyCheckpointComplete快照完成以后,调用对应的commit提交事务 , kafka就是commitTransation
initializeState从快照恢复,会先判断上次事务的状态如果还没提交会先提交