Flink流处理的容错机制
快照中核心的元素是流标记(barriers),barriers被注入到数据流中,作为数据流的一部分和其他数据一同流动(正如InfoSphere的punctuation)这些快照标记消息和其他消息一样在DAG中流动,但是不会被用户定义的业务逻辑所处理,每一个快照标记消息都将其所在的数据流分成两部分:本次快照数据和下次快照数据。每个Barrier带有一个ID,该ID为将处于该Barrier之前的数据归入快照的检查点的ID。Barrier不会打断数据流的流动,所以它是十分轻量级的。来自不同的快照的多个Barrier可以同一时间存在于同一个流中,也就是说不同的快照可以并行同时发生。
当中间一个operator接收到到所有流的快照标记,这个operator会自己保存快照标记,处理后发射出去;当sink operator已经接收到所有流的快照标记, 它将快照n通知给checkpoint coordinator。在所有Sink都通知了一个快照后,这个快照就完成了;当所有的Data Sink(终点操作符)都收到快照标记信息并对自己的状态快照和存储后,整个分布式快照就完成了,同时通知数据源释放该快照标记消息之前的所有消息。若之后发生节点崩溃等异常情况时,只需要恢复之前存储的分布式快照状态,并从数据源重发该快照以后的消息就可以了。
Flink流处理的时间窗口
对于流处理系统来说,流入的消息是无限的,所以对于聚合或是连接等操作,流处理系统需要对流入的消息进行分段,然后基于每一段数据进行聚合或是连接等操作。消息的分段即称为窗口,流处理系统支持的窗口有很多类型,最常见的就是时间窗口,基于时间间隔对消息进行分段处理。本节主要介绍Flink流处理系统支持的各种时间窗口。
对于目前大部分流处理系统来说,时间窗口一般是根据Task所在节点的本地时钟来进行切分,这种方式实现起来比较容易,不会阻塞消息处理。但是可能无法满足某些应用的要求,例如:
1. 消息本身带有时间戳,用户希望按照消息本身的时间特性进行分段处理。
2. 由于不同节点的时钟可能不同,以及消息在流经各个节点时延迟不同,在某个节点属于同一个时间窗口处理的消息,流到下一个节点时可能被切分到不同的时间窗口中,从而产生不符合预期的结果。
Flink支持三种类型的时间窗口,分别适用于用户对于时间窗口不同类型的要求:
1. Operator Time。根据Task所在节点的本地时钟来进行切分的时间窗口。
2. Event Time。消息自带时间戳,根据消息的时间戳进行处理,确保时间戳在同一个时间窗口的所有消息一定会被正确处理。由于消息可能是乱序流入Task的,所以Task需要缓存当前时间窗口消息处理的状态,直到确认属于该时间窗口的所有消息都被处理后,才可以释放其状态。如果乱序的消息延迟很高的话,会影响分布式系统的吞吐量和延迟。
3. Ingress Time。有时消息本身并不带有时间戳信息,但用户依然希望按照消息而不是节点时钟划分时间窗口(例如,避免上面提到的第二个问题)。此时可以在消息源流入Flink流处理系统时,自动生成增量的时间戳赋予消息,之后处理的流程与Event Time相同。IngressTime可以看成是Event Time的一个特例,由于其在消息源处时间戳一定是有序的,所以在流处理系统中,相对于EventTime,其乱序的消息延迟不会很高,因此对Flink分布式系统的吞吐量和延迟的影响也会更小。
Event Time时间窗口的实现
Flink借鉴了Google的MillWheel项目,通过WaterMark来支持基于EventTime时间窗口。
当操作符通过基于Event Time的时间窗口来处理数据时,它必须在确定所有属于该时间窗口的消息全部流入此操作符后,才能开始处理数据。但是由于消息可能是乱序的,所以操作符无法直接确认何时所有属于该时间窗口的消息全部流入此操作符。WaterMark包含一个时间戳,Flink使用WaterMark标记所有小于该时间戳的消息都已流入,Flink的数据源在确认所有小于某个时间戳的消息都已输出到Flink流处理系统后,会生成一个包含该时间戳的WaterMark,插入到消息流中输出到Flink流处理系统中,Flink操作符按照时间窗口缓存所有流入的消息,当操作符处理到WaterMark时,它对所有小于该WaterMark时间戳的时间窗口的数据进行处理并发送到下一个操作符节点,然后也将WaterMark发送到下一个操作符节点。
为了保证能够处理所有属于某个时间窗口的消息,操作符必须等到大于这个时间窗口的WaterMark之后,才能开始对该时间窗口的消息进行处理,相对于基于OperatorTime的时间窗口,Flink需要占用更多的内存,且会直接影响消息处理的延迟时间。对此,一个可能的优化措施是,对于聚合类的操作符,可能可以提前对部分消息进行聚合操作,当有属于该时间窗口的新消息流入时,基于之前的部分聚合结果继续计算,这样的话,只需缓存中间计算结果即可,无需缓存该时间窗口的所有消息。
对于基于Event Time时间窗口的操作符来说,流入WaterMark的时间戳与当前节点的时钟一致是最简单理想的状况了,但是在实际环境中是不可能的,由于消息的乱序以及前面节点处理效率的不同,总是会有某些消息流入时间大于其本身的时间戳,真实WaterMark时间戳与理想情况下WaterMark时间戳的差别称为Time Skew,如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/50fb89b31207b01060f7b324886aa849.png)
图5WaterMark的Time Skew图
Time Skew决定了该WaterMark与上一个WaterMark之间的时间窗口所有数据需要缓存的时间,Time Skew时间越长,该时间窗口数据的延迟越长,占用内存的时间也越长,同时会对流处理系统的吞吐量产生负面影响。
基于时间戳的排序
在流处理系统中,由于流入的消息是无限的,所以对消息进行排序基本上被认为是不可行的。但是在Flink流处理系统中,基于WaterMark,Flink实现了基于时间戳的全局排序。
Flink基于时间戳进行排序的实现思路如下:排序操作符缓存所有流入的消息,当其接收到WaterMark时,对时间戳小于该WaterMark的消息进行排序,并发送到下一个节点,在此排序操作符中释放所有时间戳小于该WaterMark的消息,继续缓存流入的消息,等待下一个WaterMark触发下一次排序。由于WaterMark保证了其之后不会出现时间戳比它小的消息,所以可以保证排序的正确性。需要注意的是,如果排序操作符有多个节点,只能保证每个节点的流出消息是有序的,节点之间的消息不能保证有序,要实现全局有序,则只能有一个排序操作符节点。