Flink流处理过程的部分原理分析
前言
在分布式领域,计算和存储一直是两大子领域。很多分布式理念在计算和存储的实现中会有着完全不同的逻辑,比如我们快照,计算框架中的快照和我们平常说的存储快照实现不同点在于哪里呢?笔者做为一个研究存储模块出身的人,最近在研读Flink流处理的部分原理,小小作番总结。很多时候,以存储的眼光来看待计算过程中的处理过程,还是有很多不一样的地方的。下文中,笔者将逐一介绍Flink流处理的一些过程分析。额外说明一点,以下内容来源于早期Flink内部设计文档,可能与现今使用的Flink有所差异,详细来源可见文末引用地址。
流的时间有序性保证
在上一篇阐述Flink窗口处理的文章中,笔者提到了关于网络传输等外部因素造成的流数据的潜在乱序问题,也就是一些“迟到“了的数据。对于这种情况,Flink在内部提供了一种有序数据流的概念,当然这个有序流是一种被二次加工过的数据流,从而保证了其有序性。那么问题来了,这种数据流是进行了何种处理方式呢?
用一句简单地话来说:通过watermark对数据重排序,来保证整体数据流的有序性。
而这里分段数据的重排序,依靠的是数据流的watermark值。每当我们每接收到一份数据到buffer中时,我们选定其中最新的watermark值,对buffer里数据的时间小于此watermark值的数据在buffer中做一个排序。然后将此排序好的数据发向下游。这里基于的一个原则是:时间比当前watermark消息早的数据都已经到来了,所以我们可以大胆地把这批数据先拍好序再发出去。图示效果如下:
窗口序列对齐
最Flink流任务中,会涉及到数据被多次窗口处理的问题,比如数据流被A窗口处理过有到看B窗口中做处理。我们如何来指定窗口的序列关系呢?
这里Flink采用了一种窗口逐一对齐的做法。后一窗口的起始末尾边界与前一序处理窗口的边界完全对齐,对应区间范围内的结果数据同样落位到相对应的区间窗口内。如下图所示:
流数据的容错:Checkpoint机制
流处理与批处理相比,它的一大优势在于它的低延时,而批处理的一个得天独厚的优势是错误恢复容易。因为批处理任务在每次的批处理操作中会保存住全部的输入数据,如果出现结果算错的情况,重新执行一次处理过程即可。而流式计算中连续不断的数据处理,使得错误恢复变得复杂起来。所以假如流处理任务能够做到快速的错误恢复,那么其可用性将会大大加强。下面笔者主要阐述的是Flink的错误恢复机制:Checkpoint机制。
首先,我们假设发生了一个流处理任务执行异常失败的场景,然后我们准备在下一刻进行完全地恢复,重新回到失败的那个时刻点,任务继续往后跑。那么在这里我们至少有保留哪些状态数据呢?答案是以下3点:
- Source的偏移量位置
- 当时正在流动中的数据
- 操作状态数据
下面我们来看看针对这2类数据,Flink内部是如何做定期checkpoint的。
Barrier
Flink为了实现定期的checkpoint,做的一个核心改动是在流数据中增加一个标记数据记录,名为stream barrier。不同时间点插入barrier数据将流数据分隔成了多份,每份对应一次checkpoint操作,同时checkpoint会保留住数据源source当时的偏移量信息。如下图所示:
当barrier标记从source流向到sink下游,并且系统受到sink端的确认消息后,此checkpoint宣告正式完成。如果过程中需要涉及多input的输入时,处理快的barrier流会在过程中等待落后的其它流直到它们的barrier信息到来,然后再往下游传输数据,如下图:
State
对于应用中所涉及的中间状态数据,Flink支持用户自定义状态持久化操作,然后应用程序在重新启动的时候从外部存储中重新恢复状态数据。
一般情况下,为了保证状态数据的一致性,checkpoint状态数据的时候是同步的过程。Flink在后来实现了一种异步状态同步的方法,主要采用的思路是拷贝原状态的数据,然后用异步线程去持久化拷贝的那份状态数据。同时为了防止每次checkpoint大量相同状态的数据,Flink在后期也实现了增量checkpoint的功能。
引用
[1].https://cwiki.apache.org/confluence/display/FLINK/Time+and+Order+in+Streams
[2].https://ci.apache.org/projects/flink/flink-docs-master/internals/stream_checkpointing.html