本文将以这些概念为基础,逐一介绍 Flink 的 发展背景、核心概念、时间推理与正确性工具、安装部署、客户端操作、编程API 等内容,让开发人员对 Flink 有较为全面的认识并拥有一些基础操作与编程能力。
一、发展背景
1.1 数据处理架构
在流处理器出现之前,数据处理架构主要由批处理器组成,其是对 无限数据的有限切分,具有 吞吐量大、数据较为准确 的特点。
然而我们知道,批处理器在时间切分点附近 仍然无法保证数据结果的真实性,且数据的时效性往往比较低,延迟大。
除了批处理之外,人们为了达到数据生成的高时效性,在数据处理架构中也常常使用微服务来解决,其特点是 延迟低、无状态、服务与存储分离。
但是微服务无状态的约束很大程度上决定了其并不能很好的应用于现代实时数据处理的需求中,比如准确一次的语义、乱序数据流的处理能力等,它无法满足人们对一个先进的流处理器的想象(在无状态的业务需求中,微服务仍然是最佳选择)。
而要满足人们的这些想象,数据处理架构恰恰需要有 「状态」 的概念和相应的机制支持才行。
由此 有状态的流处理器 开始逐渐完善并大规模使用。
有状态的流处理器依赖 高可用可重放的数据源,通过 State(状态)提供准确一次的语义,通过 时间推理工具能够还原真实世界的数据情况,广泛应用于 事件驱动(实时警报)、数据管道(实时数仓)、数据分析(实时报表) 等业务场景中。
高可用可重放的数据源:
- 持久化的、只添加的
- 写入顺序不可变
- 多个消费者多次消费
- 可信的数据源、可重放
1.2 开源分布式流处理器
开源流处理器在不断地发展,从一开始只关注低延迟指标到现在兼顾延迟、吞吐与结果准确性,在发展过程中解决了很多问题,编程API的易用性也在不断地提高。
第一代
- 关注 毫秒级延迟 的事件处理
- 数据丢失与处理简单,容易造成结果不够精确
- 牺牲了部分的准确性来换取更低的延迟
- 只有底层API接口
第二代
- 更好的容错,确保在发生故障的时候仅仅处理一次
- 提供高级API编程接口
- 可能 牺牲延迟到秒级
- 结果取决于事件到达的顺序
第三代
- 精准一次的语义,批流都可应用计算
- 兼顾延迟、吞吐与结果准确性
- 解决了依赖于时间和事件到达顺序的问题
二、核心概念
了解完流处理器的发展背景后,我们来详细讨论一下 Flink 中的核心概念,这些概念是学习与使用 Flink 十分重要的基础知识,在后续开发 Flink 程序过程中将会帮助开发人员更好地理解 Flink 内部的行为和机制。
本节中相关核心概念摘抄自 Flink最佳实践(一)流式计算系统概述 ,该文中对流处理器核心概念有详细的讨论与说明,这里不再过多累述。
2.1 Time(时间语义)
和其他流处理器一样,Flink 中的 Time 也分为三种:事件时间、达到时间与处理时间。
事件时间
事件时间是 事件真实发生的时间。
由于数据乱序的原因,服务端收到数据时的时间和事件本身的时间可能是相差极大的。
正是因为这种差异,服务端做基于事件时间的计算是 最复杂的,需要对乱序的数据流做处理以 「还原」 真实世界的情况,需要依赖一定的数据缓存。
- 迟到与乱序处理
- 延迟较高
达到时间
达到时间是 系统接收到事件的时间,即服务端接收到事件的时间。
处理时间
处理时间是 系统开始处理到达事件的时间。
在某些场景下,处理时间等于达到时间。
因为处理时间 没有乱序 的问题,所以服务端做基于处理时间的计算是比较简单的,无迟到与乱序数据。
- 不需要考虑迟到和乱序
- 有较低的延迟
Flink 中只需要通过 env
环境变量即可设置Time:
//创建环境上下文
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 设置在当前程序中使用 ProcessingTime
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime);
2.2 Window(窗口)
在时间语义(时域)之上,对数据流的操作分为与时间无关的、与时间有关的两种类型,其中与时间有关的操作都与窗口操作挂钩。
基于各类时间的窗口处理 是流处理器中主要的与时间有关的操作,窗口本质就是将无限数据集 沿着时间的边界切分成有限数据集。
没有窗口,就没法在时间维度上划分数据集,也就没法进行后续的数据操作。
在 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中两者并没有差异:
- trigger: 设置窗口触发器,没有设置则使用默认触发器
- evictor: 设置窗口触发后、计算执行前,前置的数据过滤器,没有则无
- allowedLateness: 窗口允许的最大延迟,没有则无
- sideOutputLateData: 获取延迟的数据并可以使用
reduce/aggregate/fold/apply
进行处理 - getSideOutput: 获取延迟的数据
以上方法都是可选调用。
在定义窗口之前,开发人员必须先定义数据流是否根据key分组,使用 keyBy
函数即可将数据流划分为 Keyed Stream,如果 keyBy
没有被调用,则数据流为 Non-Keyed。
在 Keyed Stream 中,所有的数据都会根据指定的key分配到并行的流中,如果你对大数据开发感兴趣,想系统学习大数据的话,可以加入大数据技术学习交流扣扣群:458数字345数字782,欢迎添加,私信管理员,了解课程介绍,获取学习资源,所以 Keyed Stream 可以进行高效的并行操作,相同key的数据将会被分配到相同的并行任务中。
在 Non-Keyed Stream 中,数据不会被分割成多个并行的逻辑流,即并行度为1。
2.3 State(状态)与Checkpoint(检查点)
Flink 中,状态用于缓存 用户数据、窗口数据、程序运行时状态、数据源偏移量 等信息,而检查点则是 定期对状态备份并提供恢复能力的机制。
正是因为有状态与检查点的支持,Flink才能做到:
- 备份与恢复、7 * 24小时运行的容错
- 数据不重复不丢失,精准一次
- 数据实时产出不延迟
- 横向扩展
- 数据之间有关联,需要通过状态满足业务逻辑
- 系统状态(窗口缓冲区)、用户自定义状态
2.4 一致性保证
一致性保证通常也被称为一致性语义,是流处理器的能力体现。
至多一次
- 事件可以被简单的丢弃
- 等于「无保证」
- 可以得到近似的结果,尽可能低的延迟
至少一次
- 所有事件都会被处理,但是可能处理了多次
- 数据源可重放即可
- 可以得到近似的结果
仅仅一次
- 数据源需要支持重放
- 处理器需要保持状态一致
三、正确性与时间推理工具
Flink 中保持强正确性的工具是 State 和 Checkpoint,提供时间推理能力的工具是 Watermark 和 Trigger。
接下来我们来详细讨论Flink中,这些工具是如何使用和实现的。
3.1 State
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访问。
支持的数据结构有:
- ValueState
- MapState
- AppendingState
等,其中 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
- 构造函数:MemoryStateBackend(int maxStateSize, boolean asyncSnapshot)
- 存储方式:State存储于各个 TaskManager内存中,Checkpoint存储于 JobManager内存
- 容量限制:单个State最大5M、maxStateSize<=akka.framesize(10M)、总大小不超过J