Flink是一个有状态的流处理框架,因此需要对状态做持久化,Flink定期保存状态数据到存储空间上,故障发生后从之前的备份中恢复,这个过程被称为Checkpoint机制。而Checkpoint为Flink提供了Exactly-Once的投递保障。
流处理是一个数据不断输入的过程,为了更好更方便的快照,需要将数据进行分批分段;而Barrier(栅栏)就是做这个事情,它将数据流分段,在进行Checkpoint的时候Flink会在数据流源头处周期性地注入Barrier,这些Barrier会作为数据流的一部分,一起流向下游节点并且不影响正常的数据流。Barrier的作用是将无界数据流从时间上切分成多个窗口,每个窗口对应一系列连续的快照中的一个,每个Barrier都带有一个快照ID,一个Barrier生成之后,在这之前的数据都进入此快照,在这之后的数据则进入下一个快照。
如图所示,当ID为n的Checkpoint Barrier到达每个算子后,表示要对n-1和n之间状态更新做Snapshot。
一个完整的Flink作业包括Source和Sink两大模块,Source和Sink肩负着Flink与外部系统进行数据交互的重要功能,它们又被称为外部连接器(Connector)。
Flink的Checkpoint过程保证了一个作业内部的数据一致性,主要是因为Flink对如下两类数据做了备份。
端到端的Exactly-Once问题是分布式系统领域最具挑战性的问题之一,很多系统都在试图攻克这个问题。在这个问题上,Flink内部状态的一致性主要依赖Checkpoint机制,外部交互的一致性主要依赖Source和Sink提供的功能。Source需要支持重发功能,Sink需要采用一定的数据写入技术,比如幂等写或事务写。
对于Source重发功能,如图7-1所示,只要我们记录了输入的偏移量Offset,作业重启后数据发送方根据该Offset重新开始发送数据即可。Kafka的Producer除了发送数据,还能将数据持久化写到日志文件中。如果下游作业重启,Kafka Producer根据下游作业提供的Offset,从持久化的日志文件中定位到数据,可以重新开始向下游作业发送数据。
幂等写(Idempotent Write)是指,任意多次向一个系统写入数据,只对目标系统产生一次结果影响。
事务(Transaction)是数据库系统所要解决的核心问题。Flink借鉴了数据库中的事务处理技术,同时结合自身的Checkpoint机制来保证Sink只对外部输出产生一次影响。
简单概括,Flink的事务写(Transaction Write)是指,Flink先将待输出的数据保存下来,暂时不向外部系统提交;等到Checkpoint结束,Flink上、下游所有算子的数据都一致时,将之前保存的数据全部提交到外部系统。换句话说,只有经过Checkpoint确认的数据才向外部系统写入。如图所示,在数据重发的例子中,如果使用事务写,那只把时间戳3之前的输出提交到外部系统,时间戳3以后的数据(例如时间戳5和8生成的数据)先被写入缓存,等得到确认后,再一起提交到外部系统。这就避免了时间戳5的数据多次产生输出,并多次提交到外部系统。
在事务写的具体实现上,Flink目前提供了两种方式:预写日志(Write-Ahead-Log,WAL)和两阶段提交(Two-Phase-Commit,2PC)。这两种方式也是很多数据库和分布式系统实现事务时经常采用的方式,Flink根据自身的条件对这两种方式做了适应性调整。这两种方式的主要区别在于:Write-Ahead-Log方式使用Operator State缓存待输出的数据;如果外部系统自身支持事务,比如Kafka,就可以使用Two-Phase-Commit方式,待输出数据被缓存在外部系统。