Flink-1.12.1源码阅读之精确一次消费
flink中精确一次消费的范畴要比普通的诸如spark streaming,kafka来的更广泛,而且flink确实是这么做的.
Flink提供了基于二阶段提交的分布式锁,可拿来直接使用,也是相当便捷的,但是这种使用也是有前提的.
1 关于精确一次消费的解释
精确一次消费的概念出现在消息系统中,通常关注点只和消息的消费有关,但是实际情况远不止如此.
站在使用者的角度来说,精确一次消费包括了从消息源发出消息,处理程序接到消息并处理消息乃至到最后保存结果,真个流程是事物性质的,只有如此才能做到精确一次消费,既不存在重复消费,又不存在遗漏消费,只有精确一次的消费.
然而站在程序运行的角度来说,消息源的程序运行和消息的消费以及最终结果的保存都是不同的程序在运行,也就是上述使用者所期望的一个完整的事物是由三个相互独立同时互不关联的部分所组成,因此若想实现事务性,实际上很难做到.
比如kafka和spark streamming,它们都是精确一次消费的,但是都是在消息进入各自的程序到消息流出为止的这一时期内.而消息的源头和最终保存这两个阶段,它们都不保证是精确一次消费,它们也没法保证.
所以flink结合消息源和持久化端给出了基于二阶段提交的分布式锁,把三阶段统一起来,这样就实现了整个流程的精确一次消费,当然前提是消息源和持久化端要能支持事务,否则也是不行的.
2 flink二阶段提交的实现
2.1 TwoPhaseCommitSinkFunction类概述
这里flink提供的TwoPhaseCommitSinkFunction类就是二阶段提交的工具,FlinkKafkaProducer使用了该类,这里会详细给出其使用方式.
该类的方法有
该类是虚类,一共有5个虚方法,需要在使用时实现这些虚方法.FlinkKafkaProducer就继承了该类,下面看一下其具体实现方式.
2.2 FlinkKafkaProducer类解析
对应其虚方法,首先是beginTransaction.
可见,在开始事务时,第一步只是把kafka的producer开启.
其次是preCommit方法.
此时什么也没做,猜测应该是在invoke中来完成.
接着是invoke方法.
Invoke主要就是执行逻辑的方法了,首先创建了record,就是为之后发送消息做准备,其次判断了一些设置,根据不同情况赋值record,我这里把这部分代码折叠起来了,主要就是看最后最重要的send操作,这里就把record发送出去了.
然后就是commit方法.
这里也仅仅是提交了事务.
最后就是abort方法.
这里也只是把事务取消掉,同时再次发送record而已.
2.3 小结
通过flink自己的FlinkKafkaProducer类的实现,可以看到,在二阶段提交中重要的方法是beginTransaction和invoke,前者是连接kafka这个数据源并为之后发送消息做准备,而invoke则是具体的实现发消息的逻辑.
整体上来看,对一个操作比如是数据源发消息,使用TwoPhaseCommitSinkFunction是比较简单的,主要实现了beginTransaction和invoke方法即可.
3 补充说明
目前,就剩下connectors工程没有分析了,该工程主要是数据源和持久化端的类型,比如kafka等,包含的类型有很多,大概有40多种.如果类型支持事务,则给出了类似kafka的事务性实现,来保证精确一次消费,具体可以查看flink官网的说明.
这里不再详细说明这些connector的实现,读者可以参照上面给出的kafka的source代码自行查看.
4 总结
如果数据源头支持事务,那么使用TwoPhaseCommitSinkFunction来实现精确一次发送消息,如果持久化端支持事务,使用该类实现数据落地精确一次消费,结合消息处理引擎自身的事务性,那么从源头到消费到持久化全程就实现了精确一次消费.flink官网的文档也给出目前源头和持久端都可以配合实现精确一次消费的类型.