1 在checkpoint之间是一直pre_commit, 数据写入kafka broker, 同时在transaction协调器以及 消费者处理器之间写入本次事务的元信息;等到本次从source到sink之间的checkpoint全部完成,job manager完成checkpoint时,sink的producer才正式发出事务commit,这时候kafka之前写入broker的log才会对消费者显示有效可以读取。
以下信息取自:https://zhuanlan.zhihu.com/p/111304281
具体代码层面:
flink kafkasink的TwoPhaseCommitSinkFunction仍然留了以下四个抽象方法待子类来实现:
beginTransaction():开始一个事务,返回事务信息的句柄。
preCommit():预提交(即提交请求)阶段的逻辑。
commit():正式提交阶段的逻辑。
abort():取消事务
这四个方法实际上是flink对于sink端支持两阶段事务提交中间件的抽象,具体针对kafka sink来说,应用了kafka对应的两阶段提交的方法。
1.1 预提交阶段
FlinkKafkaProducer011.preCommit()方法的实现很简单。其中的flush()方法实际上是代理了KafkaProducer.flush()方法。
那么preCommit()方法是在哪里使用的呢?答案是TwoPhaseCommitSinkFunction.snapshotState()方法。从前面的类图可以得知,TwoPhaseCommitSinkFunction也继承了CheckpointedFunction接口,所以2PC是与检查点机制一同发挥作用的。
每当需要做checkpoint时,JobManager就在数据流中打入一个屏障(barrier),作为检查点的界限。屏障随着算子链向下游传递,每到达一个算子都会触发将状态快照写入状态后端(state BackEnd)的动作。当屏障到达Kafka sink后,触发preCommit(实际上是KafkaProducer.flush())方法刷写消息数据,但还未真正提交。接下来还是需要通过检查点来触发提交阶段。
1.2 提交阶段
FlinkKafkaProducer011.commit()方法实际上是代理了KafkaProducer.commitTransaction()方法,正式向Kafka提交事务。
该方法的调用点位于TwoPhaseCommitSinkFunction.notifyCheckpointComplete()方法中。顾名思义,当所有检查点都成功完成之后,会回调这个方法。
该方法每次从正在等待提交的事务句柄中取出一个,校验它的检查点ID,并调用commit()方法提交之。
1.3 回退阶段
可见,只有在所有检查点都成功完成这个前提下,写入才会成功。这符合前文所述2PC的流程,其中JobManager为协调者,各个算子为参与者(不过只有sink一个参与者会执行提交)。一旦有检查点失败,notifyCheckpointComplete()方法就不会执行。如果重试也不成功的话,最终会调用abort()方法回滚事务。
2 kafka的幂等性可以防止重复提交。每个producer可以用户显式的指定transactionId, 这样producer Id以及topic、partition和生产者发送的每条消息的递增的seq Id.这些可以唯一确定一条消息,相当于唯一键,防止broker内部数据重复写入。