一、分层 API Flink 自底向上在不同的抽象级别提供了多种 API,并且针对常见的使用场景开发了专用的扩展库。每一种 API 在简洁性和表达力上有着不同的侧重,并……
一、分层 API
Flink 自底向上在不同的抽象级别提供了多种 API,并且针对常见的使用场景开发了专用的扩展库。每一种 API 在简洁性和表达力上有着不同的侧重,并且针对不同的应用场景。
简单的理解:
- 越顶层越抽象,表达含义越简明,使用越方便
- 越底层越具体,表达含义越丰富,使用越灵活
1.1 ProcessFunction
ProcessFunction 是 Flink 所提供的最具表达力的接口。ProcessFunction 可以处理一或两条输入数据流中的单个事件或者归入一个特定窗口内的多个事件。它提供了对于时间和状态的细粒度控制。开发者可以在其中任意地修改状态,也能够注册定时器用以在未来的某一时刻触发回调函数。因此,你可以利用 ProcessFunction 实现许多有状态的事件驱动应用所需要的基于单个事件的复杂业务逻辑。
下面的代码示例展示了如何在 KeyedStream 上利用 KeyedProcessFunction 对标记为 START 和END 的事件进行处理。当收到 START 事件时,处理函数会记录其时间戳,并且注册一个时长4小时的计时器。如果在计时器结束之前收到 END 事件,处理函数会计算其与上一个 START 事件的时间间隔,清空状态并将计算结果返回。否则,计时器结束,并清空状态。
这个例子充分展现了 KeyedProcessFunction 强大的表达力,也因此是一个实现相当复杂的接口。
1.2 DataStream API
实际上,大多数应用并不需要上述的底层抽象,而是针对核心 API(Core APIs)进行编程,核心API 指的是 DataStream API(有界或无界流数据)以及 DataSet API(有界数据集)。这些 API 为数据处理提供了通用的构建模块,比如由用户定义的多种形式的转换(transformations),连接(joins),聚合(aggregations),窗口操作(windows)等等。DataSet API 为有界数据集提供了额外的支持,例如循环与迭代。
DataStream API 为许多通用的流处理操作提供了处理原语,其实就是在 ProcessFunction 基础上多了许多算子。这些操作包括窗口、逐条记录的转换操作,在处理事件时进行外部数据库查询等。DataStream API 支持 Java 和 Scala 语言,预先定义了例如 map()、reduce()、aggregate() 等函数。你可以通过扩展实现预定义接口或使用Java、Scala 的 lambda 表达式实现自定义的函数。
下面的代码示例展示了如何捕获会话时间范围内所有的点击流事件,并对每一次会话的点击量进行计数。
1.3 SQL & Table API
Flink 支持两种关系型的 API,Table(DSL) API 和 SQL 。这两个 API 都是批处理和流处理统一的API,这意味着在无边界的实时数据流和有边界的历史记录数据流上,关系型 API 会以相同的语义执行查询,并产生相同的结果。Table API 和 SQL 借助了 Apache Calcite 来进行查询的解析,校验以及优化。它们可以与 DataStream 和 DataSet API 无缝集成,并支持用户自定义的标量函数,聚合函数以及表值函数。
Flink 的关系型 API 旨在简化数据分析、数据流水线和 ETL 应用的定义。
下面的代码示例展示了如何使用 SQL 语句查询捕获会话时间范围内所有的点击流事件,并对每一次会话的点击量进行计数。此示例与上述 DataStream API 中的示例有着相同的逻辑。
二、 流处理
在 Flink 中,应用程序由用户自定义算子转换而来的流式 Dataflows 所组成。这些流式 Dataflows形成了有向图,以一个或多个源(Source)开始,并以一个或多个汇(Sink)结束。
Flink 应用程序可以消费来自消息队列或分布式日志这类流式数据源(例如 Apache Kafka 或Amazon Kinesis)的实时数据,也可以从各种的数据源中消费有界的历史数据。同样,Flink 应用程序生成的结果流也可以发送到各种数据汇中。
Flink 程序本质上是分布式并行程序。在程序执行期间,一个流有一个或多个流分区(StreamPartition),每个算子有一个或多个算子子任务(Operator Subtask)。每个子任务彼此独立,并在不同的线程中运行,或在不同的计算机或容器中运行。简而言之就是一个应用程序称之为 Flink-Job,一个Flink-Job 包含多个 Task,每个 Task 包含多个 SubTask。
算子子任务数就是其对应算子的并行度。在同一程序中,不同算子也可能具有不同的并行度。
Flink 算子之间可以通过一对一(直传)模式或重新分发模式传输数据:
- 一对一模式(例如上图中的 Source 和 map() 算子之间)可以保留元素的分区和顺序信息。这意味着 map() 算子的 Subtask[1] 输入的数据以及其顺序与 Source 算子的 Subtask[1] 输出的数据和顺序完全相同,即同一分区的数据只会进入到下游算子的同一分区。
- 重新分发模式(例如上图中的 map() 和 keyBy/window 之间,以及 keyBy/window 和 Sink 之间)则会更改数据所在的流分区。当你在程序中选择使用不同的 transformation,每个算子子任
务也会根据不同的 transformation 将数据发送到不同的目标子任务。例如以下这几种transformation 和其对应分发数据的模式:keyBy()(通过散列键重新分区)、broadcast()(广播)或 rebalance()(随机重新分发)。在重新分发数据的过程中,元素只有在每对输出和输入子任务之间才能保留其之间的顺序信息(例如,keyBy/window 的 Subtask[2] 接收到的 map() 的Subtask[1] 中的元素都是有序的)。因此,上图所示的 keyBy/window 和 Sink 算子之间数据的重新分发时,不同键(key)的聚合结果到达 Sink 的顺序是不确定的
三、 并行度
并行度(Parallelism)是一种动态概念,表示 TaskManager 运行程序时的并发能力,直观理解就是一个 Task 设定了几个 SubTask。
- 算子并行度 > TaskSlot 数量 :集群的实际并行能力是 TaskSlot 的数量;
- 算子并行度 < TaskSlot 数量 :集群的实际并行能力是算子并行度。
注意:在实际使用过程中,并行度 > TaskSlot 会直接抛出异常。
还没有评论呢,快来抢沙发~