[Note] Apache Flink 的数据流编程模型

时间:2022-03-01 16:28:49

Apache Flink 的数据流编程模型

抽象层次

Flink 为开发流式应用和批式应用设计了不同的抽象层次

[Note] Apache Flink 的数据流编程模型

[Note] Apache Flink 的数据流编程模型

状态化的流

抽象层次的最底层是状态化的流,它通过 ProcessFunction 嵌入到 DataStream API 中,允许用户*地处理来自一个或多个流的事件(event)以及使用一致的容错状态

此外,用户可以注册事件时间并处理时间回调(callback),这使得程序可以处理更复杂的计算

核心 API

大多数情况下用户不直接在上面描述的这种低的抽象层面上编程,取而代之的是使用所谓的核心 API,包括可以处理 Unbounded 和 Bounded 流的 DataStream API 和处理 Bounded 数据集的 DataSet API

这些 API 提供了数据处理通用的构建手段,包括用户定义的各种类型的转换(transformation),连接(join),聚合(aggregation),Windows 和状态等,经由这些 API 处理的数据类型被表示为所使用的编程语言中的类(class)

低抽象层次的处理函数被整合到 DataStream API 中以实现对特定操作的精细处理

DataSet API 为 Bounded 数据集提供了额外的原语(primitive),包括循环(loop)和迭代(iteration)

Table API

Table API 是处理表的一种 DSL,表代表着流,而Table API 可以动态地修改表

Table API 使用扩展的关系模型,表关联着模式,就像关系数据库中的表一样,API 提供了对应的操作,包括 select,project,join,group-by 和 aggregate 等等

Table API 写成的程序使用声明式的语法指示操作应该在逻辑上怎么运行,而不是具体地指定操作应该怎么应用

虽然 Table API 可以通过用户定义的函数进行拓展,但它的表达能力还是比不上核心 API,不过 Table API 的优点在于使用起来非常简洁,此外,Table API 写成的程序在执行前会被特定的优化器优化

Table API 和 DataStream/DataSet API 可以混用,这是因为,由于对偶性,表和 DataStream/DataSet 可以无缝地转换

SQL

Flink 提供的最高抽象层次是 SQL,这层抽象在语义和表现上都类似于 Table API,但是语法上表现为 SQL 语句

SQL 抽象和 Table API 密切关联,并且 SQL 查询可以在 Table API 定义的表上进行

程序与数据流

Flink 程序的基本块包括流(stream)和转换(transformation),即使是使用 DataSet API 操作的 DataSet,在内部也表现为流

从概念上说,流(stream)是源源不断的数据记录流(flow),转换(transformation)是接收若干个流为输入,产生若干个流为输出的操作,程序中的转换通常和流中的转换操作一一对应,但也可能对应多个转换操作

Flink 程序在执行时被映射为 Streaming Dataflow,包括流(stream)和转换操作(transformation operator),每个数据流开始于若干个源(source)并结束于若干个汇(sink)

数据流表现为任意有向无环图(DAG),虽然通过迭代结构可以产生一些环,但是简单起见我们可以不太在意它

[Note] Apache Flink 的数据流编程模型

[Note] Apache Flink 的数据流编程模型

并行的数据流

Flink 的程序自动地是并行的和分布的,在执行的时候,每个流被分成若干个流的部分(stream partition),每个操作对应着若干个子操作(operator subtask)

子操作之间是相互独立的,运行在不同的线程上,这意味着有可能运行在不同的机器或容器上

特定操作的子操作数量代表了它的并行度,流的并行度由它的 Producing Operator 决定,同一个程序的不同操作可能有不同的并行度

[Note] Apache Flink 的数据流编程模型

[Note] Apache Flink 的数据流编程模型

流传输数据有两种模式,①一对一的模式 ②重新分配的模式

一对一的流,例如上图中 Sourcemap() 的过程,保持了元素的分组和顺序,也就是说 map()[1] 看到的元素包括顺序都和 Source[1] 一样

重新分配的流,例如上图中 map()keyBy/window() 的过程和 keyBy/window()sink() 的过程,会改变流的划分,每个操作的子任务,根据选定的转换,向不对应的下一个操作的子任务发送数据,例如 keyBy() 使用键的哈希值重新分配,boardcast() 做全频广播,rebalance() 做随机重新分配

窗口(Windows)

事件的聚合,例如 count 和 sum,在流处理和批处理上是不一样的,比如说,我们没办法统计流上的所有的元素,在流上做聚合的一般方法是通过 windows,例如统计最近五分钟的元素或计算最近一百个元素的和

Windows 可以是时间驱动或数据驱动的,类似于上面的两个例子

Windows 有不同的类型,包括滚动窗口(tumbling windows),滑动窗口(sliding windows)和会话窗口(session windows

时间(Time)

流式程序中的时间通常包括以下几种概念

  1. 事件时间(event time)指的是一个事件被创建的时间,通常由事件中的时间戳(timestamp)描述,例如传感器或服务器,Flink 通过 Timestamp Assigners 获取时间戳

  2. 摄入时间(ingestion time)指的是事件从 Flink 数据流中的源进入的时间

  3. 处理时间(processing time)指的是执行具体操作的机器的本地时间

[Note] Apache Flink 的数据流编程模型

[Note] Apache Flink 的数据流编程模型

状态化的操作

数据流中的操作有些对于每次处理每个事件都是单独的,不过也有些需要记住多个事件之间的信息,例如在 windows 操作中,这些操作被称为状态化的

状态化的操作的状态由嵌入式的键值对维护,状态被划分并分布到对应的流中,被状态化操作读取,因此表示为键值对的状态仅能在键化(keyed)的流中获取,首先经过一个 keyBy 函数,然后通过当前事件的键获取

流状态和键的对齐使得状态更新都是本地操作,这就使得一致性无需额外的事务开销,还能支持 Flink 透明地重新分配和调整划分

容错机制中的检查点

Flink 使用流重放(stream replay)和检查点(checkpointing)技术的组合来支持容错

检查点与每个输入流中的特定点和每个操作对应的状态有关,可以通过回滚操作的状态并从检查点重放事件来恢复流式数据流,同时维持一致性,保证 Excatly-once 处理的语义

容错点的间隔是对执行中容错的保障和等待恢复时间的权衡,等待恢复的时间主要取决于需要重放的事件数

流上的批处理

Flink 将批处理程序作为流处理程序的特例来执行,DataSet 在内部作为流式数据处理,因此上面提到的技术也以类似的形式应用到批处理上,大同小异,主要的不同点如下

批处理的容错机制没有检查点,从错误中恢复是通过完全重放整个流来实现的,虽然这使得恢复的时间成本增大,但是因为不需处理检查点,可以加速正常处理流程

DataSet API 中的状态化操作使用简化的内存/外核数据结构取代键/值索引

DataSet API 提供了基于超步(superstep)的专门的同步的迭代过程,这种过程只能在 Bounded 的流上执行