这是我参与「第四届青训营 」笔记创作活动的第3天
简介
Flink作为强大的流批一体处理引擎为人所知晓和使用,其中,其流处理的高效性和高正确性很大程度上得益于对Exactly Once的实现。在本文中,我总结了字节跳动第四届青训营对于相关内容的讲解,进行了梳理,重点介绍Exacly Once的实现原理及相关知识点。
目录
- 不同数据处理保证的语义
- 状态快照与恢复
- Checkpoint的使用
- Flink中Exactly Once的实现
- 总结
不同数据处理保证的语义
首先需要了解的是,在数据源源不断到来的过程中,对于数据的处理,即动态表,是与之相交融的,也就是说,数据流和动态表之间存在如下的转换:
graph TD
Stream --> DynamicTable --> ContinuousQuery --> DynamicTable' --> Stream'
现在的问题便是我们要怎样处理这些数据。
一共有三种不同的数据处理保证:
- At-most-once:指处理流程在出现故障的时候,什么都不做,等到系统恢复,该继续做什么工作便继续做什么,所以每条数据被处理至多一次,或者不被处理,不能保证任何语义。处理时延低。
- At-least-once:顾名思义,每条数据被处理至少一次,但是也能存在一条数据的重复消费,产生浪费。
- Exactly-once:每条数据都会被处理且就被处理一次,是最严格的处理语义,从效果上讲就仿佛故障并没有发生过。
状态快照与恢复
首先我们看上游的具体结构,由input stream, source, sum, storage, JobManager组成。
处理状态会被储存到Storage中,JobManager通过读取Storage中储存的状态也可以知道作业状态。
那么现在的问题是如果处理过程中途出现问题,怎么知道恢复到什么时间点?为了保证统一和易于检测,我们需要以每次source保留状态为基准。状态恢复的时间点即是需要等到所有处理逻辑消费完成source保留状态之前的数据。
因此,为了保证这样的统一性,一个简单的算法是:
- 暂停处理输入的数据(上游)
- 等待后续全部的算子消费完当前已经输入的数据
- 待步骤2处理结束后,作业中的所有蒜子复制自己的状态并保存到远端的可靠储存中
- 恢复处理
但是以上的算法中被等待浪费的时间很多,效率很低,所以我们在此引入运用checkpoint的Chandy-Lamport算法和Flink对于Exactly Once的实现方法,在下文中分别介绍。
Checkpoint的使用
为了防止上游算子在下游算子处理的时候停止工作浪费时间,同时又可以准确记录每一波数据的处理状态,以及应对多个source的情况,Chandy-Lamport算法引入了Checkpoint
这样每个checkpoint都对应相关的储存在storage中的source,同时每个source在保存完自己的状态之后,会向下游发送checkpoint barrier,并告知Job Manager自己这一步已经完成。soure连接的算子会等待全部上游的checkpoint到达之后再开始快照的制作。而这样source只要完成以上自己的工作,就可以继续处理之后的数据,不用再等待下游算子了。
等到所有算子都告知Job Manager checkpoint标识部分已完成,整个checkpoint的过程就结束了。如出现问题,会依照checkpoint的标识回滚。
相比于最直接的算法,checkpoint的使用可以大大增加效率,然而在快照制作和等待barrier的过程中仍然存在暂停处理数据的问题,并且保存到远端的storage也可能需要很多时间,而且checkpoint虽然保证了source到source连接的算子的同步,sink可能同样会有不同步的现象。于是Flink在实现Exactly Once上又做了进一步优化。
Flink中Exactly Once的实现
为了实现端到端的Exactly Once语义,需要特殊的Sink算子。
为了协调每个节点都能保持同步,同时执行或回滚某个操作,Flink引入了一个中心节点,称为协作者(Coordinator),而被调度的节点称为参与者(Participant)。协作者与参与者之间的互动遵循两阶段提交协议:
及每个sink算子先进入一个“预释放”状态,如果没有出现问题可以commit,对协作者vote yes告知自己的状态,如果所有参与者都vote yes,那么协作者会通知sink算子可以释放,即第二阶段,sink算子可以进行释放。
两阶段的总体流程如下:
总结
如上,本文主要讨论了为实现Exactly Once语义,checkpoint的使用和Flink特殊算子以及两阶段提交协议。各部分具体的实现方式还需要日后更具体的了解甚至读一些源码。
我个人对于Exactly Once在Flink中的实现存在的一个小疑惑是,在两阶段提交协议中,coordinator等待全部participant vote yes,这个对于sink带来的“预释放”和释放之间的时间差会有怎样的影响?