Exactly Once语义在Flink中的实现|青训营笔记

193 阅读5分钟

这是我参与「第四届青训营 」笔记创作活动的第3天

简介

Flink作为强大的流批一体处理引擎为人所知晓和使用,其中,其流处理的高效性和高正确性很大程度上得益于对Exactly Once的实现。在本文中,我总结了字节跳动第四届青训营对于相关内容的讲解,进行了梳理,重点介绍Exacly Once的实现原理及相关知识点。

目录

  1. 不同数据处理保证的语义
  2. 状态快照与恢复
  3. Checkpoint的使用
  4. Flink中Exactly Once的实现
  5. 总结

不同数据处理保证的语义

首先需要了解的是,在数据源源不断到来的过程中,对于数据的处理,即动态表,是与之相交融的,也就是说,数据流和动态表之间存在如下的转换:

graph TD
Stream --> DynamicTable --> ContinuousQuery --> DynamicTable' --> Stream'

现在的问题便是我们要怎样处理这些数据。

一共有三种不同的数据处理保证:

  1. At-most-once:指处理流程在出现故障的时候,什么都不做,等到系统恢复,该继续做什么工作便继续做什么,所以每条数据被处理至多一次,或者不被处理,不能保证任何语义。处理时延低。
  2. At-least-once:顾名思义,每条数据被处理至少一次,但是也能存在一条数据的重复消费,产生浪费。
  3. Exactly-once:每条数据都会被处理且就被处理一次,是最严格的处理语义,从效果上讲就仿佛故障并没有发生过。

状态快照与恢复

首先我们看上游的具体结构,由input stream, source, sum, storage, JobManager组成。

IMG_1805.jpg

处理状态会被储存到Storage中,JobManager通过读取Storage中储存的状态也可以知道作业状态。

那么现在的问题是如果处理过程中途出现问题,怎么知道恢复到什么时间点?为了保证统一和易于检测,我们需要以每次source保留状态为基准。状态恢复的时间点即是需要等到所有处理逻辑消费完成source保留状态之前的数据。

因此,为了保证这样的统一性,一个简单的算法是:

  1. 暂停处理输入的数据(上游)
  2. 等待后续全部的算子消费完当前已经输入的数据
  3. 待步骤2处理结束后,作业中的所有蒜子复制自己的状态并保存到远端的可靠储存中
  4. 恢复处理

但是以上的算法中被等待浪费的时间很多,效率很低,所以我们在此引入运用checkpoint的Chandy-Lamport算法和Flink对于Exactly Once的实现方法,在下文中分别介绍。

Checkpoint的使用

为了防止上游算子在下游算子处理的时候停止工作浪费时间,同时又可以准确记录每一波数据的处理状态,以及应对多个source的情况,Chandy-Lamport算法引入了Checkpoint

IMG_1806.jpg

这样每个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)。协作者与参与者之间的互动遵循两阶段提交协议:

IMG_1807.jpg

及每个sink算子先进入一个“预释放”状态,如果没有出现问题可以commit,对协作者vote yes告知自己的状态,如果所有参与者都vote yes,那么协作者会通知sink算子可以释放,即第二阶段,sink算子可以进行释放。

两阶段的总体流程如下:

IMG_1808.jpg

IMG_1809.jpg

总结

如上,本文主要讨论了为实现Exactly Once语义,checkpoint的使用和Flink特殊算子以及两阶段提交协议。各部分具体的实现方式还需要日后更具体的了解甚至读一些源码。

我个人对于Exactly Once在Flink中的实现存在的一个小疑惑是,在两阶段提交协议中,coordinator等待全部participant vote yes,这个对于sink带来的“预释放”和释放之间的时间差会有怎样的影响?