本文将以这些概念为基础,逐一介绍 Flink 的 发展背景、核心概念、时间推理与正确性工具、安装部署、客户端操作、编程API 等内容,让开发人员对 Flink 有较为全面的认识并拥有一些基础操作与编程能力。
在流处理器出现之前,数据处理架构主要由批处理器组成,其是对 无限数据的有限切分,具有 吞吐量大、数据较为准确 的特点。
然而我们知道,批处理器在时间切分点附近 仍然无法保证数据结果的真实性,且数据的时效性往往比较低,延迟大。
除了批处理之外,人们为了达到数据生成的高时效性,在数据处理架构中也常常使用微服务来解决,其特点是 延迟低、无状态、服务与存储分离。
但是微服务无状态的约束很大程度上决定了其并不能很好的应用于现代实时数据处理的需求中,比如准确一次的语义、乱序数据流的处理能力等,它无法满足人们对一个先进的流处理器的想象(在无状态的业务需求中,微服务仍然是最佳选择)。
而要满足人们的这些想象,数据处理架构恰恰需要有 「状态」 的概念和相应的机制支持才行。
由此 有状态的流处理器 开始逐渐完善并大规模使用。
有状态的流处理器依赖 高可用可重放的数据源,通过 State(状态)提供准确一次的语义,通过 时间推理工具能够还原真实世界的数据情况,广泛应用于 事件驱动(实时警报)、数据管道(实时数仓)、数据分析(实时报表) 等业务场景中。
高可用可重放的数据源:
开源流处理器在不断地发展,从一开始只关注低延迟指标到现在兼顾延迟、吞吐与结果准确性,在发展过程中解决了很多问题,编程API的易用性也在不断地提高。
第一代
第二代
第三代
了解完流处理器的发展背景后,我们来详细讨论一下 Flink 中的核心概念,这些概念是学习与使用 Flink 十分重要的基础知识,在后续开发 Flink 程序过程中将会帮助开发人员更好地理解 Flink 内部的行为和机制。
本节中相关核心概念摘抄自 Flink最佳实践(一)流式计算系统概述 ,该文中对流处理器核心概念有详细的讨论与说明,这里不再过多累述。
和其他流处理器一样,Flink 中的 Time 也分为三种:事件时间、达到时间与处理时间。
事件时间
事件时间是 事件真实发生的时间。
由于数据乱序的原因,服务端收到数据时的时间和事件本身的时间可能是相差极大的。
正是因为这种差异,服务端做基于事件时间的计算是 最复杂的,需要对乱序的数据流做处理以 「还原」 真实世界的情况,需要依赖一定的数据缓存。
达到时间
达到时间是 系统接收到事件的时间,即服务端接收到事件的时间。
处理时间
处理时间是 系统开始处理到达事件的时间。
在某些场景下,处理时间等于达到时间。
因为处理时间 没有乱序 的问题,所以服务端做基于处理时间的计算是比较简单的,无迟到与乱序数据。
Flink 中只需要通过 env
环境变量即可设置Time:
//创建环境上下文
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 设置在当前程序中使用 ProcessingTime
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime);
在时间语义(时域)之上,对数据流的操作分为与时间无关的、与时间有关的两种类型,其中与时间有关的操作都与窗口操作挂钩。
基于各类时间的窗口处理 是流处理器中主要的与时间有关的操作,窗口本质就是将无限数据集 沿着时间的边界切分成有限数据集。
没有窗口,就没法在时间维度上划分数据集,也就没法进行后续的数据操作。
在 Flink 中,当 属于这个窗口的第一个元素到达时就会创建一个窗口。
当时间(事件或处理时间)超过窗口的结束时间戳加上用户指定的最大允许延迟时间时,窗口就会被完全删除。
这就是 Flink 窗口的生命周期。
Flink 中的每个窗口都有一个 触发器和执行函数:
除此之外,窗口还可以定义一个 回收器,用来在 窗口触发后、计算执行前(后) 排除或者回收指定的元素。
Keyed 与 Non-Keyed Windows
Flink 中有两大类型的窗口:Keyed Windows 和 Non-keyed Windows,两种类型的窗口操作API有细微的差别:
在 Keyed Windows 中,stream 需要通过 keyBy
和 window
方法生成,而在 Non-Keyed Windows 中,stream 只需要通过 windowAll
方法即可生成。
在随后的API中两者并没有差异:
reduce/aggregate/fold/apply
进行处理以上方法都是可选调用。
在定义窗口之前,开发人员必须先定义数据流是否根据key分组,使用 keyBy
函数即可将数据流划分为 Keyed Stream,如果 keyBy
没有被调用,则数据流为 Non-Keyed。
在 Keyed Stream 中,所有的数据都会根据指定的key分配到并行的流中,如果你对大数据开发感兴趣,想系统学习大数据的话,可以加入大数据技术学习交流扣扣群:458数字345数字782,欢迎添加,私信管理员,了解课程介绍,获取学习资源,所以 Keyed Stream 可以进行高效的并行操作,相同key的数据将会被分配到相同的并行任务中。
在 Non-Keyed Stream 中,数据不会被分割成多个并行的逻辑流,即并行度为1。
Flink 中,状态用于缓存 用户数据、窗口数据、程序运行时状态、数据源偏移量 等信息,而检查点则是 定期对状态备份并提供恢复能力的机制。
正是因为有状态与检查点的支持,Flink才能做到:
一致性保证通常也被称为一致性语义,是流处理器的能力体现。
至多一次
至少一次
仅仅一次
Flink 中保持强正确性的工具是 State 和 Checkpoint,提供时间推理能力的工具是 Watermark 和 Trigger。
接下来我们来详细讨论Flink中,这些工具是如何使用和实现的。
3.1.1 状态类别
Flink 有两种状态提供给开发人员使用:Managed State 和 Raw State。
Managed State
Managed State 是由flink runtime管理来管理的,自动存储、自动恢复,在内存管理上有优化机制。
且 Managed State 支持常见的多种数据结构,如value、list、map等,在大多数业务场景中都有适用之处。
总体来说是对开发人员来说是比较友好的,因此 Managed State 是 Flink 中最常用的状态。
Managed State 又分为 Keyed State 和 Operator State 两种。
Keyed State 只能用在 KeyedStream 上的算子中,每个key对应一个state,可以通过flink runtimecontext访问。
支持的数据结构有:
等,其中 AppendingState 还有不同的子类实现,详细的使用信息可以参考 Flink官网。
而 Operator State 可用于所有算子(常用于Source),一个Operator实例对应一个State,支持的数据结构:List 等。
Raw State
Raw State 由用户自己管理,需要序列化,只能使用字节数组的数据结构。
Raw State 的使用和维度都比 Managed State 要复杂,建议在自定义的Operator场景中酌情使用。
3.1.2 状态存储
Flink中状态的实现有三种:MemoryState、FsState、RocksDBState。
三种状态存储方式与使用场景各不相同,详细介绍如下:
MemoryStateBackend