本文介绍了Apache Flink的定义、架构、基本原理,并辨析了大数据流计算相关的基本概念。同时回顾了大数据处理方式的历史演进以及有状态的流式数据处理的原理。最后,分析了Apache Flink作为业界公认为最好的流计算引擎之一所具备的天然优势,旨在帮助读者更好地理解大数据流式处理引擎的基本概念,更好地应用Flink。
当我们考虑状态容错时,难免会想到精确一次的状态容错,它应用于运算时累积的状态。每个输入事件都反映到状态上,每次更改状态都是精确一次。如果修改超过一次,则数据引擎产生的结果就会变得不可靠。
为了确保状态具有精确一次的容错保证,我们需要使用一些技术,例如记录每个状态更改的时间戳,并跟踪每个状态更改的来源。这些技术可以帮助我们检测和纠正任何重复的状态更改。
在分布式场景下,我们需要为多个具有本地状态的运算子生成一个全局一致的快照。这可以通过使用一些分布式快照算法来实现,例如Chandy-Lamport算法或Distributed Snapshot Protocol。
最重要的是,我们需要在不中断运算的前提下生成快照。这可以通过使用一些基于增量的快照算法来实现,例如基于增量的Chandy-Lamport算法或基于增量的Distributed Snapshot Protocol。这些算法允许我们在运算过程中生成快照,而不会影响运算的进行。
在以上场景中,如果某个使用者的出现次数计算不准确,那么产生的结果就无法作为参考。为确保状态容错的精度,在考虑容错保证之前,首先需要考虑最简单的使用场景:无限流的数据进入,通过单一的Process进行运算,每处理完一笔数据即会累积一次状态。为确保Process产生精确的状态容错,可以在每处理完一笔数据后,更改完状态后进行一次快照。将快照存储在队列中,并对相应的状态进行对比,以完成一致的快照。通过这种方式就能确保精确一次的状态容错。
在Flink中,由于是分布式的处理引擎,会涉及到多个本地状态的运算。如果想要在不中断运算值的前提下产生全域一致的快照,则需要进行分散式状态容错。
在分布式环境中,如果想要实现Global consistent snapshot,需要确保每一笔数据的快照点是连续的,并且所有运算值都能够被正确地处理、更改和维护状态。
这可以在多个节点上进行运算,每个节点都能获取全局快照并保存状态。这样,对于每一个运算值,都可以知道它的状态和位置,从而实现全局一致的快照。当然,这种方法在简单场景下也是适用的。
分散式状态容错 - 容错恢复:
Flink使用基于simple lamport演算法机制的方法,在不中断运算的情况下持续产生Global consistent snapshot。这个方法通过在某个Datastream中安插Checkpoint barrier来实现,Checkpoint barrier会不断地插入到数据流中,形成N一1的结构,其中N代表所有在这个范围内的数据都是Checkpoint barrier N。这种方法可以保证数据流的高可用性和数据的可靠性,同时降低了重复率。
分散式快照(Distributed Snapshots)
在Flink中,产生Checkpoint是由job manager触发的,Checkpoint的触发会从数据源开始产生Checkpoint barrier。当开始做Checkpoint barrier N时,可以将其视为需要逐步填充左下角的表格。
如图,若将部分事件标为红色,同时Checkpoint barrier N也是红色,则这些数据或事件都由Checkpoint barrier N负责,而Checkpoint barrier N后面白色部分则不属于它的管辖范围。
根据上述规则,当数据源收到Checkpoint barrier N后,它会先保存自己的状态,比如读取Kafka资料时,它会保存自己在Kafka分区的读取进度,并将此状态写入上文提到的表格中。在此之后,数据源会将数据发送给下游,下游会根据这个状态来读取数据,从而实现数据的顺序性。
当Checkpoint barrier N到达下游Operator1时,它会对属于Checkpoint barrier N的所有数据进行运算,并将运算结果更新到状态中。在这之后,Operator1也会将这些数据的处理结果一起写入到Checkpoint中。同时,当Operator1接收到Checkpoint barrier N时,也会将状态快照保存到Checkpoint中。
当完成快照后,下游的Operator2接收到所有数据并搜索Checkpoint barrier N的数据,并将其反映到状态中。当状态接收到Checkpoint barrier N时,也会将其写入到Checkpoint N中。
这样,完成以上过程后就得到了一个名为Distributed Snapshots(分布式快照)的表格。分布式快照可用于状态容错,因为如果某个节点发生故障,可以从早期的Checkpoint中恢复。当多个Checkpoint同时进行时,如Checkpoint N+1和Checkpoint N+2等,Flink job manager可以触发它们的生成。利用这种机制,可以在不阻塞运算的情况下持续地生成Checkpoint。
状态维护即用一段代码在本地维护状态值,当状态值非常大时需要本地的状态后端来支持。
在Flink程序中,可以使用getRuntimeContext().getState(desc) API注册状态。Flink支持多种状态后端,而通过状态后端读取状态值是在API注册状态后实现的。Flink有两种不同类型的状态值,以及两种不同类型的状态后端。
JVM Heap状态后端适用于状态比较小的情况,可以在每次需要读取状态时使用Java对象读写操作读取或写入状态值,因此不会产生较大的代价。但是当需要将本地状态放入分布式快照时,就需要进行序列化操作。
RocksDB状态后端是一种out-of-core的状态后端,可以将状态维护在磁盘中,因此在需要读取状态时可能需要进行序列化和反序列化的过程。当需要进行快照时,仅需序列化应用数据,并直接将序列化后的数据传输到中央共享DF$中即可。相较于在Runtime本地状态后端维护状态的情况,RocksDB可以大大降低对内存的消耗,适合大规模需求的状态存储。
纯内存的状态后端和具有资源磁盘的状态后端。用户可以根据状态数量的多少,选择采用较为适合的状态后端。纯内存的状态后端维护状态时速度较快,但会在状态量较大时消耗过多的内存资源。而具有资源磁盘的状态后端则可以在状态量较大时将状态数据放在磁盘中进行维护,从而减少对内存的消耗。用户可根据不同的应用场景,选择性地采用适合的状态后端,以达到更好的性能。
本文主要针对于Flink技术架构中的【事件与时间维度分析】的要点处理模式,包含:不同时间种类、Event-Time的处理、Watermarks水印以及状态保存和迁移。
在Flink及其他流式处理引擎出现之前,大数据处理引擎只支持Processing-time的处理。如果定义了一个运算窗口,假如将该窗口设定为每小时进行结算,使用Processing-time进行运算时,发现数据引擎将在3点至4点间收集到的数据进行结算。但是实际上,当做报表或者分析结果时,我们更关心的是在3点至4点之间实际产生的数据的输出结果,而想要了解实际数据的输出结果,我们需要采用Event-Time的方式进行处理。这是因为Event-Time 是基于数据的真实时间戳来进行运算处理,在计算窗口时会考虑事件发生的时间,所以能够更加准确地反映出事件发生的真实情况。
在图中,Event-Time指的是事件发生的时间戳。数据在最开始的队列中被接收后,会被划分为不同的批次,随后进行Event-Time Process处理。这一处理过程会根据事件发生的时间戳对数据进行运算。具体来说,每隔一小时,数据都会被划分为一个新的批次。
Event-Time 是基于数据的真实时间戳来进行运算处理。在事件发生的时间上进行 Re-bucketing,将对应时间段(例如3点到4点)的数据放在相应时间的 Bucket 中,并在该时间段结束后产生计算结果。因此,Event-Time 和 Processing-Time 的概念之间的对比存在差异。Processing-Time 指数据到达处理引擎的时间,而不是事件实际发生时间,因此进行处理时往往会导致数据的不准确性。
Event-Time的重要性在于准确地记录引擎输出运算结果的时间,因此可以更加有效地进行数据处理和分析。
举个例子,如果一个流式引擎连续24小时在运行、搜集资料,并且在Pipeline中有一个 windows Operator 正在进行运算,每小时能够产生结果。
为了得到准确的结果,在 windows Operator 完成运算并输出运算值的时间点记录 Event-Time 是非常关键的。这个时间点表示该收的数据已经到位,可以进行下一步的数据处理和分析。在实时数据处理中,Event-Time的准确性对于后续数据分析和预测非常重要。
Flink实际上是用watermarks来实现Event-Time的功能。Watermarks在Flink中也属于特殊事件,其精髓在于当某个运算值收到带有时间戳“T”的watermarks时就意味着它不会接收到新的数据了。
Flink 中实际上是使用 Watermarks 来实现 Event-Time 的功能。Watermarks 是 Flink 中的一种特殊事件,其功能在于为流处理过程中的事件引入一种时间概念,并且在 Flink 的时间语义中起到关键性的作用。
Watermarks的作用是当某个运算值收到带有时间戳“T”的 Watermarks 时,就意味着它不会再接收到新的数据了,也就是说它可以开始进行下一步的处理。通过 Watermarks 和 Event-Time 结合起来使用,可以更加准确地处理实时数据,在数据处理效率和准确性上得到更好的保障。
使用 Watermarks 的好处在于可以更准确地预估数据的到达时间,从而更加准确地掌握数据处理进展情况。假设预计数据的到达时间与输出结果的时间差为 5 分钟,在Flink中所有的 Window Operator 会搜索 3 点至 4 点的数据,但是由于存在延迟需要再额外等待 5 分钟才能收集完毕4:05分的数据,因此Watermarks的作用就在于在这个时间段内对数据进行处理。
当运算值接收到时间戳为4:05的Watermarks 时,才能判定4点钟的数据收集完成,然后才会产出 3 点至 4 点的数据结果。这个时间段的结果是由 Watermarks 来标识的。
在流式处理应用中,有时需要更改应用的逻辑或修复 bug,此时需要将前一个执行状态迁移到新的执行状态,可以通过使用 Flink 的 Savepoint 来实现。同时,如果需要重新定义应用程序的并发度,可以通过更改 Flink 中的并发度参数来重新定义应用程序的并行度。这个参数可以在 Flink 的配置文件中设置,或者在创建 Flink 应用程序时动态指定。
如果需要升级运算集群的版本号,在 Flink 中可以使用升级工具来进行升级操作。升级工具会将现有的运算集群停止,然后将其升级到指定的版本。在升级过程中,Flink 会自动将现有的应用程序状态保存下来,以便升级完成后可以继续进行处理。
保存点(Savepoint):一个手动产生的检查点(Checkpoint),保存点记录着流式应用中所有运算元的状态。
在实际的流式处理应用中,Savepoint 的产生非常重要,可以帮助应用实现状态迁移、处理质量的提升等功能。它的实现原理是通过手动在 Checkpoint barrier 流中插入分布式快照点,产生 Savepoint。Savepoint 可以保存在任何位置,并且在变更应用时,可以直接从 Savepoint 恢复执行。
需要注意的是,在从 Savepoint 恢复执行时,时间是持续前进的,因此需要确保恢复到的是最新的数据,才能保证处理结果的一致性。此外,在重新运算时,可以使用 Process Event-Time 或 Event-Time+Buckting 策略,以确保运算结果的一致性。其中,Event-Time 策略适用于窗口大小较小的情况,而 Event-Time+Buckting 策略适用于窗口大小较大或重新运算的数据量较大的情况。
通过合理地使用 Savepoint,我们可以轻松实现流式处理应用的状态管理,从而提高应用的可靠性和处理质量。
Processing-time:在大数据处理引擎只支持Processing-time的情况下,定义运算窗口时可以设定结算时间,例如每小时进行一次结算。但如果使用Processing-time进行运算,会发现所有在3点至4点间收集到的数据只会在下一个结算周期被结算。
对比Processing-time,当我们需要对在3点至4点之间实际产生的数据进行报表或者分析时,使用Processing-time进行运算可能不够准确。相反,我们需要采用Event-Time的方式进行处理。
Event-Time是基于数据的真实时间戳来进行运算处理,因此在计算窗口时会考虑事件发生的时间,能够更准确地反映事件发生的真实情况,从而提供更准确的分析结果。
在Flink中实现Event-Time的功能,需要使用watermarks。因为watermarks是一种特殊的事件,它的作用是通知Flink的运算值,在当前时刻之前窗口中的所有事件数据都已经到达,因此不会再接收到新的数据。这样,Flink就能够在安全的情况下处理来自Event-Time的数据。因此,使用watermarks是实现Flink的Event-Time的关键。
Flink中提供了Savepoint来实现状态迁移和处理逻辑更改。在流式处理应用中,有时候需要修改应用的逻辑或修复bug,这时就需要将前一个执行状态迁移到新的执行状态。除此之外,如果需要重新定义应用程序的并发度,也可以通过更改Flink中的并发度参数来实现。这个参数可以在Flink的配置文件中设置,或者在创建Flink应用程序时动态指定。通过调整并行度,可以实现对Flink应用程序的优化。