1.2.3 核心概念
1.有状态计算
在Flink架构体系中,有状态计算是非常重要的特性之一。如图1-10所示,有状态计算是指在程序计算过程中,程序内部存储计算产生的中间结果,并将其提供给后续的算子进行计算。状态数据可以存储在本地内存中,也可以存储在第三方存储介质中,例如Flink已经实现的RocksDB。
图1-10 有状态处理和无状态处理
和有状态计算不同,无状态计算不会存储计算过程中产生的结果,也不会将结果用于下一步计算。程序只会在当前的计算流程中执行,计算完成就输出结果,然后接入下一条数据,继续处理。
无状态计算实现的复杂度相对较低,实现起来也比较容易,但是无法应对比较复杂的业务场景,例如处理实时CEP问题,按分钟、小时、天进行聚合计算,求取最大值、均值等聚合指标等。如果不借助Flink内部提供的状态存储,一般都需要通过外部数据存储介质,常见的有Redis等键值存储系统,才能完成复杂指标的计算。
和Storm等流处理框架不同,Flink支持有状态计算,可以应对更加复杂的数据计算场景。第6章将重点讲解Flink中有状态计算的实现以及对整个系统状态数据实现容错保障等。
2.时间概念与水位线机制
在DataFlow模型中,时间会被分为事件时间和处理时间两种类型。如图1-11所示,Flink中的时间概念基本和DataFlow模型一致,且Flink在以上两种时间概念的基础上增加了进入时间(ingestion time)的概念,也就是数据接入到Flink系统时由源节点产生的时间。
图1-11 Flink时间概念
事件时间指的是每个事件在其生产设备上发生的时间。通常在进入Flink之前,事件时间就已经嵌入数据记录,后续计算从每条记录中提取该时间。基于事件时间,我们可以通过水位线对乱序事件进行处理。事件时间能够准确地反映事件发生的先后关系,这对流处理系统而言是非常重要的。在涉及较多的网络传输时,在传输过程中不可避免地会发生数据发送顺序改变,最终导致流系统统计结果出现偏差,从而很难通过实时计算的方式得到正确的统计结果。
处理时间是指执行相应算子操作的机器系统时间。当应用基于处理时间运行时,所有基于时间的算子操作(如时间窗口)将使用运行相应算子机器的系统时钟。例如,应用程序在上午9:15运行,则第一个每小时处理时间窗口包括在上午9:15到上午10:00之间处理的事件,下一个窗口包括在上午10:00到11:00之间处理的事件。
处理时间是最简单的时间概念,不需要在流和机器之间进行协调,它提供了最佳的性能和最低的延迟。但在分布式和异步环境中,处理时间不能提供确定性,因为它容易受到记录到达系统的速度(例如从消息队列到达系统)以及系统内算子之间流动速度的影响。
接入时间是指数据接入Flink系统的时间,它由SourceOperator自动根据当前时钟生成。后面所有与时间相关的Operator算子都能够基于接入时间完成窗口统计等操作。接入时间的使用频率并不高,当接入的事件不具有事件时间时,可以借助接入时间来处理数据。
相比于处理时间,接入时间的实现成本较高,但是它的数据只产生一次,且不同窗口操作可以基于统一的时间戳,这可以在一定程度上避免处理时间过度依赖处理算子的时钟的问题。
不同于事件时间,接入时间不能完全刻画出事件产生的先后关系。在Flink内部,接入时间只是像事件时间一样对待和处理,会自动分配时间戳和生成水位线。因此,基于接入时间并不能完全处理乱序时间和迟到事件。