

# Dataflow 编程模型

## 抽象层次

Flink提供了不同级别的抽象来开发流/批处理应用程序。

![编程抽象层](https://ci.apache.org/projects/flink/flink-docs-release-1.7/fig/levels_of_abstraction.svg)

*   最低层次的抽象仅提供 **有状态流**. 它通过 [Process Function](../dev/stream/operators/process_function.html)嵌入到 [DataStream API](../dev/datastream_api.html) . 它允许用户自由处理来自一个或多个流的事件，并使用一致的容错 _state_。此外，用户可以注册事件时间和处理时间回调，允许程序实现复杂的计算。

*   在实践中，大多数应用程序不需要上面描述的低级抽象，而是根据**Core APIs**编写程序，比如[DataStream API](../dev/datastream_api.html)(有界/无界流)和[DataSet API](../dev/batch/index.html)(有界数据集)。这些连贯api为数据处理提供了通用的构建块，比如各种形式的用户指定的转换、连接、聚合、窗口、状态等。在这些api中处理的数据类型表示为各自编程语言中的类。

    低级的 _Process Function_ 函数与 _DataStream API_ 集成，使得仅对某些操作进行低级抽象成为可能。_DataSet API_ 在有界数据集上提供了额外的原语，比如循环/迭代。

*   **Table API** 是一个以 _tables_ 为中心的声明性DSL，它可以动态地更改表(在表示流时)。[Table API](../dev/table_api.html)遵循(扩展的)关系模型:表具有附加的模式(类似于关系数据库中的表)，API提供了类似的操作，如select、project、join、group-by、aggregate等。表API程序声明性地定义了应该做什么逻辑操作，而不是确切地指定操作代码的样子。尽管表API可由各种类型的用户定义函数扩展，但它的表现力不如 _Core APIs_ ，但使用起来更简洁(编写的代码更少)。此外，Table API程序在执行之前还会经过一个应用优化规则的优化器。

    可以在表和 _DataStream_/_DataSet_ 之间无缝转换，允许程序混合 _Table API_ 和 _DataStream_ 和 _DataSet_ api。

*   Flink提供的最高级别抽象是**SQL**。这种抽象在语义和表达性上都类似于 _Table api_ ，但是将程序表示为SQL查询表达式。[SQL](../dev/table_api.html#sql)抽象与表API紧密交互，SQL查询可以在 _Table API_ 中定义的表上执行。

## 程序和数据流

Flink程序的基本构建块是**streams**和 **transformations**。(请注意，Flink的DataSet API中使用的数据集也是内部流——稍后将对此进行更多介绍)。从概念上讲，_stream_是一个(潜在的永无休止的)数据记录流，_transformation_ 是一个以一个或多个流作为输入并生成一个或多个输出流的操作。

当执行时，Flink程序被映射到 **streaming dataflows**，包括**streams**和转换**operators**。每个数据流从一个或多个**sources**开始，以一个或多个**sinks**结束。数据流类似于任意**directed acyclic graphs** _(DAGs)_。尽管通过 _iteration_ 构造允许特殊形式的循环，但为了简单起见，我们将在大多数情况下对此进行解释。

![一个数据流程序及其数据](https://ci.apache.org/projects/flink/flink-docs-release-1.7/fig/program_dataflow.svg)

通常在程序中的转换和数据流中的操作符之间存在一对一的对应关系。然而，有时一个转换可能由多个转换操作符组成。

源和接收器在[流连接器](../dev/connectors/index.html)和[批连接器](../dev/batch/connectors.html) 文档中有文档说明。转换被记录在[DataStream操作符](//ci.apache.org/projects/flink/flink-docs-release-1.7/dev/stream/operators/index.html)和[数据集转换](../dev/batch/dataset_transformations.html)中。

## 并行数据流

Flink中的程序本质上是并行和分布式的。在执行期间，_stream_ 有一个或多个**stream partitions**，每个 _operator_ 有一个或多个**operator subtasks**。运算符子任务彼此独立，在不同的线程中执行，也可能在不同的机器或容器中执行。

运算符子任务的数量是该运算符的**parallelism**。流的并行性始终是其生成操作符的并行性。同一程序的不同运算符可能具有不同级别的并行性。

![并行数据流](https://ci.apache.org/projects/flink/flink-docs-release-1.7/fig/parallel_dataflow.svg)

流可以在_一对一(或 _forwarding_ )模式或 _redistributing_ 模式中的两个操作符之间传输数据:

*   **One-to-one**流(例如上图中的 _Source_ 和 _map()_ 操作符之间的流)保留元素的分区和顺序。这意味着 _map()_ 操作符的子任务[1]将以与 _Source_ 操作符的子任务[1]生成的元素相同的顺序看到这些元素。

*   **Redistributing**流(如上面的 _map()_ 和 _keyBy/window_ 之间以及 _keyBy/window_ 和 _Sink_ 之间)更改流的分区。每个 _operator subtask_ 将数据发送到不同的目标子任务，具体取决于所选的转换。例如 _keyBy()_ (通过散列键重新分区)、_broadcast()_ 或 _rebalance()_ (随机重新分区)。在更改 _redistributing_ 中，元素之间的顺序仅保存在每对发送和接收子任务中(例如，_map()_ 的子任务[1]和 _keyBy/window_ 的子任务[2])。所以在这个例子中，每个键内的顺序是保留的，但是并行性确实引入了关于不同键的聚合结果到达sink的顺序的不确定性。

关于配置和控制并行性的详细信息可以在[parallel execution](../dev/parallel.html)。

## Windows

聚合事件(例如, counts, sums)在流上的工作方式与在批处理中不同。例如，不可能计算流中的所有元素，因为流通常是无限的(无界的)。相反，流上的聚合(计数、和等)的作用域是**windows**，例如 _“count over the last 5 minutes”_ 或 _“sum of the last 100 elements”_。

Windows可以是 _time driven_ (示例:每30秒)或 _data driven_ (示例:每100个元素)。通常可以区分不同类型的窗口，例如 _scroll windows_ (没有重叠)、_滑动windows_(有重叠)和 _session windows_ (中间有不活动的间隙)。

![Time- and Count Windows](https://ci.apache.org/projects/flink/flink-docs-release-1.7/fig/windows.svg)

更多窗口示例可以在这篇找到 [blog post](https://flink.apache.org/news/2015/12/04/Introducing-windows.html) 。更多细节在[window docs](../dev/stream/operators/windows.html)中。

## Time

当在流媒体程序中引用时间(例如定义窗口)时，可以引用不同的时间概念:

*   **Event Time**是创建事件的时间。它通常由事件中的时间戳描述，例如由生产传感器或生产服务附加的时间戳。Flink通过[timestamp assigners](//ci.apache.org/projects/flink/flink-docs-release-1.7/dev/event_timestamps_watermarks.html)访问事件时间戳。

*   **Ingestion time**是事件在源操作符上进入Flink数据流的时间。

*   **Processing Time**是执行基于时间的操作的每个操作符的本地时间。

![Event Time, Ingestion Time, and Processing Time](https://ci.apache.org/projects/flink/flink-docs-release-1.7/fig/event_ingestion_processing_time.svg)

关于如何处理时间的更多细节见[event time docs](//ci.apache.org/projects/flink/flink-docs-release-1.7/dev/event_time.html)。

## 有状态操作

虽然数据流中的许多操作每次只查看单个 _event at a time_(例如事件解析器)，但有些操作会记住多个事件(例如窗口操作符)之间的信息。这些操作称为**stateful**。

有状态操作的状态是在可以认为是嵌入式键/值存储区中维护的。状态与有状态操作符读取的流一起严格地分区和分布。因此，只能在 _keyBy()_ 函数之后的 _keyed streams_ 上访问键/值状态，并且只能访问与当前事件的键关联的值。对流和状态的键进行对齐可以确保所有的状态更新都是本地操作，从而保证一致性而不存在事务开销。这种对齐还允许Flink重新分配状态并透明地调整流分区。

![State and Partitioning](https://ci.apache.org/projects/flink/flink-docs-release-1.7/fig/state_partitioning.svg)

有关更多信息，请参阅有关的文档 [state](../dev/stream/state/index.html).

## 容错检查点

Flink使用 **stream replay** 和**checkpointing**的组合实现容错。检查点与每个输入流中的特定点以及每个操作符的对应状态相关。流数据流可以从检查点恢复，同时通过恢复操作符的状态并从检查点重新播放事件来保持一致性 _(exactly-once processing semantics)_。

检查点间隔是一种平衡执行期间容错开销和恢复时间(需要重播的事件数量)的方法。

[fault tolerance internals](//ci.apache.org/projects/flink/flink-docs-release-1.7/internals/stream_checkpointing.html) 提供了关于Flink如何管理检查点和相关主题的更多信息。有关启用和配置检查点的详细信息见 [checkpointing API docs](../dev/stream/state/checkpointing.html)。

## 流式批处理

Flink将 [batch programs](../dev/batch/index.html) 作为流程序的一种特殊情况执行，流是有界的(元素的数量是有限的)。_DataSet_ 在内部作为数据流处理。因此，上述概念同样适用于批处理程序，也适用于流媒体程序，但有少数例外:

*   [Fault tolerance for batch programs](../dev/batch/fault_tolerance.html)不使用检查点。恢复是通过完全重播流来实现的。这是可能的，因为输入是有界的。这使得恢复的成本更高，但是常规处理更便宜，因为它避免了检查点。

*   数据集API中的有状态操作使用简化的内存/内核数据结构，而不是键/值索引。

*   数据集API引入了特殊的同步(基于超步的)迭代，这只有在有界流上才能实现。有关详细信息，请查看[iteration docs](//ci.apache.org/projects/flink/flink-docs-release-1.7/dev/batch/iterations.html)。

## 下一章

继续Flink的基本概念[Distributed Runtime](runtime.html).

