在计算机领域,很多时候,我们学习新工具,第一个学习内容就是官方文档。按着官方学习推荐路线走准没错的。
学习使用 Flink 第一步,官方推荐我们要了解一些基本概念,特别是 Dataflow Programming Model (数据流编程模型)和 Distributed Runtime Environment(分布式执行环境)
这两个概念太重要了,将会分两篇文章来,现在这篇文章将会重点介绍 Flink 的编程模型。
抽象层次
Flink提供不同级别的抽象来开发流/批处理应用程序。
- 有状态的数据流处理层(Stateful Stream Processing)。最底层的抽象仅仅提供有状态的数据流,它通过处理函数(Process Function)嵌入到数据流api(DataStream API). 用户可以通过它自由的处理单流或者多流,并保持一致性和容错。同时用户可以注册事件时间和处理时间的回调处理,以实现复杂的计算逻辑。
- 核心 API 层(DataStream / DataSet API)。 它提供了数据处理的基础模块,像各种transformation, join,aggregations,windows,stat 以及数据类型等等
- Table API 层。 定了围绕关系表的DSL(领域描述语言)。Table API 遵循了关系模型的标准:Table类型关系型数据库中的表,API也提供了相应的操作,像select,project,join,group-by,aggregate等。Table API 声明式的定义了逻辑上的操作(logical operation)不是 code for the operation;Flink 会对Table API逻辑在执行前进行优化。同时代码上,Flink 允许混合使用 Table API 和 DataStram/DataSet API
- SQL 层。 它很类似 Table API 的语法和表达,也是定义与 Table API 层次之上的,但是提供的是纯 SQL 的查询表达式。
程序和数据流
用户实现的Flink程序是由 Stream 和 Transformation 这两个基本构建块组成,其中Stream 是一个中间结果数据,而 Transformation 是一个操作,它对一个或多个输入Stream 进行计算处理,输出一个或多个结果 Stream。当一个 Flink 程序被执行的时候,它会被映射为 Streaming Dataflow。一个 Streaming Dataflow 是由一组 Stream 和Transformation Operator (算子)组成,它类似于一个 DAG 图,在启动的时候从一个或多个Source Operator 开始,结束于一个或多个 Sink Operator。
上图中,FlinkKafkaConsumer 是一个 Source Operator,而 map、keyBy、timeWindow、apply 是 Transformation Operator,同时 RollingSink 是一个Sink Operator。
并行数据流
在 Flink 中,程序天生是并行和分布式的:一个 Stream 可以被分成多个 Stream 分区(Stream Partitions),一个 Operator 可以被分成多个 Operator Subtask,每一个Operator Subtask 是在不同的线程中独立执行的。一个 Operator 的并行度,等于Operator Subtask 的个数,一个 Stream 的并行度总是等于生成它的 Operator 的并行度。有关 Parallel Dataflow 的实例,如下图所示:
上图 Streaming Dataflow 的并行视图中,展现了在两个 Operator 之间的 Stream 的两种模式:
- One-to-one 模式:比如从Source[1]到map()[1],它保持了Source的分区特性(Partitioning)和分区内元素处理的有序性,也就是说map()[1]的Subtask看到数据流中记录的顺序,与Source[1]中看到的记录顺序是一致的。
- Redistribution 模式:这种模式改变了输入数据流的分区,比如从map()[1]、map()[2]到keyBy()/window()/apply()[1]、keyBy()/window()/apply()[2],上游的 Subtask 向下游的多个不同的 Subtask 发送数据,改变了数据流的分区,这与实际应用所选择的 Operator 有关系。
- 另外,Source Operator对应2个 Subtask,所以并行度为2,而 Sink Operator 的 Subtask只有1个,故而并行度为1。
Windows (窗口)
聚合事件(例如,计数,总和)在流上的工作方式与批处理方式不同。例如,不可能计算流中的所有数据元,因为流通常是无限的(无界)。所以流上的聚合(计数,总和等)由窗口限定,例如“在最后5分钟内计数”或“最后100个数据元的总和”。
Windows 可以是时间驱动的(例如:每30秒)或数据驱动(例如:每100个数据元)。一个典型地区分不同类型的窗口,例如翻滚窗口(没有重叠), 滑动窗口(具有重叠)和会话窗口(由不活动的间隙打断)。
Time(时间)
当在流程序中引用时间(例如定义窗口)时,可以参考不同的时间概念:
- 事件时间 Event Time:事件的创建时间,它通常由事件中的时间戳描述,例如由生产传感器或生产服务附加。
- 摄入时间 Ingestion time: 事件进入Flink 数据流的 source 的时间。
- 处理时间 Processing Time:Processing Time 表示某个 Operator 对事件进行处理时的本地系统时间(是在TaskManager节点上)。
Stateful Operations(有状态的算子操作)
在流处理中,有些操作仅仅在某一时间针对单一事件(如事件转换map),有些操作需要记住多个事件的信息并进行处理(window operators),后者的这些操作称为有状态的操作。
有状态的操作一般被维护在内置的 key/value 存储中。这些状态信息会跟数据流一起分区并且分布存储,并且可以通过有状态的数据操作来访问。因此这些 key/value 的状态信息仅在带 key 的数据流(通过 keyBy()
函数处理过)中才能访问到。数据流按照key排列能保证所有的状态更新都是本地操作,保证一致性且无事务问题。同时这种排列方式使Flink能够透明的再分发状态信息和调整数据流分区。
Checkpoints for Fault Tolerance(容错检查点)
Flink 通过流回放和设置检查点的方式实现容错。一个 checkpoint 关联了输入流中的某个记录和相应状态和操作。数据流可以从 checkpoint 中进行恢复,并保证一致性(exactly-once 的处理语义)。 Checkpoint 的间隔关系到执行是的容错性和恢复时间。
流上的批处理
Flink 把批处理作为特殊的流处理程序来执行,许多概念也都可以应用的批处理中,除了一些小的不同:
- 批处理的API(DataSet API )不使用checkpoints,恢复通过完整的流回放来实现;
- DataSet API的有状态操作使用简单的内存和堆外内存 的数据结构,而不是 key/value 的索引;
- DataSet API 引入一种同步的迭代操作,这个仅应用于有界数据流。
👊 结束。
本文由 Chakhsu Lau 创作,采用 知识共享署名4.0 国际许可协议进行许可。
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。