Dataflow Programming Model
抽象层级
Flink提供不同的抽象层级来开发流/批处理应用。
- 最低层次的抽象只提供有状态的流。它可以通过处理函数嵌入到DataStream API中。它允许用户从一个流或者多个流中自由的处理事件,并使用一致的容错状态。另外,用户可以注册事件时间和处理时间回调函数,从而允许程序实现复杂的计算。
- 实际上,大多数应用不需要上述的低层次的抽象,而是会针对核心的API,如DataStream API(有界的/无界的流)和DataSet API(有界数据集)进行编程。这些API为数据处理提供通用的功能,比如各种用户指定的转换,连接,聚合,窗口和状态等等。API的这些数据类型处理功能体现在各自编程语言的类中。
低层次的处理函数与DataStream API相结合,使得针对这些低层次抽象的指定操作成为可能。DataSet API为有界的数据集提供了额外的原语,比如循环/迭代。 -
Table API是以表为中心的声明式DSL,它可以动态的改变表(当表示流时)。Table API遵循(扩展)关系模型:表都有对应的schema(类似关系数据库中的表),并且API提供兼容的操作,例如select,project,join,group-by,aggregate等。Table API声明式的定义了逻辑操作应该做什么而不是精确的支出操作的代码应该是什么样的。虽然Table API 可以被用户定义的各种类型的函数扩展,但它的表达能力不如核心API,但是使用起来更简洁(更少的代码量)。另外,Table API在执行前会通过优化器进行优化。
可以在表和DataStream/DataSet之间无缝地转换,允许程序将Table API与DataStream和DataSet API混合在一起。 - Flink提供的最高层次的抽象是SQL。这个抽象在语义和表达上类似于Table API,但是表示程序是SQL查询表达式。SQL抽象与Table API紧密地交互,并且SQL查询可以在Table API定义的表上执行。
程序与数据流
Flink程序的基本构建块是流和转换。(注意,Flink的DataSet API使用的DataSet在内部也是流——详见后述)概念上流是数据记录(可能是永无休止的)的流动,转换是一个操作,它将一个或多个流作为输入,并且产生一个或多个流作为结果。
当执行时,Flink程序被映射到数据流上,它由流和转换操作组成。每个数据流从一个或多个源开始,以一个或多个sink结束。数据流类似有向无环图(DAG)。尽管通过迭代可以构建特殊形式的循环,但是在大多数情况下,我们会为了简单而忽略它。
通常程序中的转换和数据流上的操作是一一对应的。然而有些时候一个转换可能由多个转换操作组成。
Source和Sink记录在流连接器和批处理连接器文档中。转换记录在DataStream操作和DataSet转换文档中。
并行数据流
Flink程序本质上是并行的和分布式的。在执行期间,一条流有一个或多个流分区,并且每个操作有一个或多个操作子任务。操作子任务间彼此独立,可能在不同的线程,机器或者容器中执行。
操作子任务梳理就是特定操作的并行度。流的并行度通常是它上的操作的并行度。同一个程序的不同操作可能有不同级别的并行度。
流可以在一对一(或转发)模式之间传输数据,也可以在重新分配模式中传输数据:
- 一对一的流(例如上图中的Source和map()操作之间)保持元素的分区和排序。这意味着map()操作的subtask[1]会以相同顺序收到Source操作产生的相同的元素。
-
重新分配流(就像上图中的map()和keyBy/window操作之间,以及keyBy/window和Sink之间)改变流的分区。每个操作子任务根据所选择的转换将数据发送到不同的目标子任务。示例是keyBy()(根据key进行哈希重新分区), broadcast(), 或者rebalance() (随机重新分区)。在一个重新分配的交换中,元素之间的顺序只保存在每一个对发送和接收子任务中(例如,map()的subtask[1]和keyBy/window的subtask[2])。因此,在这个例子中,每个key的顺序都保留了,但是在到达sink的不同key的聚集结果的顺序,由于并行性引入了不确定性。
有关配置和控制并行性的细节可以在并行执行的文档中找到。
窗口
聚合事件(例如,计数,求和)在流中工作方式不同于批处理。例如,不可能计算流中的所有元素,因为流通常是无限的(无界的)。相反,流上聚合(计数,求和等)由窗口覆盖,例如“计数最后5分钟”,或者“对最后100个元素求和”。
窗口可以是时间驱动的(例如,每30秒)或者数据驱动(例如,每100个元素)。一个典型的区别是不同类型的窗口,例如滚动窗口(没有重叠),滑动窗口(有重叠),和会话窗口(中间有一个不活动的间隙)。
更多的窗口例子可以在这篇博文中找到。更多的细节在窗口文档中。
More window examples can be found in this blog post. More details are in the window docs.
时间
当提到流媒体程序中的时间(例如定义windows)时,可以引用不同的时间概念:
- 事件时间是事件的创建时间。它通常由事件中的时间戳来描述,例如由生产传感器或生产服务所连接的时间戳。Flink通过时间戳来访问事件时间戳。
- 摄入时间是在源操作符处事件进入Flink数据流的时间。
-
处理时间是执行基于时间的操作的每个操作的本地时间。
如何处理时间的细节在事件时间文档中。
有状态的操作
虽然数据流上的很多操作在某个时刻只是简单的关注一个事件(例如事件解析器),但是一些操作记录跨多个事件的信息(例如窗口操作)。这些操作称为有状态的。
有状态操作的状态维护在嵌入式的key/value存储中。状态被分区并且与有状态操作符读取的流严格地分布在一起。因此在keyBy()函数后,访问key/value状态只能在key相关的流中,仅限于与当前事件的key相关的value。对齐流的key和状态使得所有的状态更新都是本地操作,确保一致性没有事务开销。这一对齐也允许Flink重新分配状态,并透明的调整流分区。
更多的细节,参见状态文档。
容错机制的检查点
Flink使用流回放和检查点组合来实现容错。检查点与每个输入流中的特定点以及每个操作符对应的状态相关。通过恢复操作符的状态,并从检查点的点重新播放事件,可以从检查点恢复流数据流,同时保持一致性(精确一次的处理语义)。
检查点的间隔意味着执行期间发生容错,恢复过来的时间开销(需要重新播放的事件数量)。
内部容错的提供了更多Flink如何管理检查点和相关主题的更多信息。关于如何开启和配置检查点的细节在检查点API文档中。
流批量处理
Flink执行批处理当做一个流式程序的一个特例,即流是有界的(有限数量的元素)。一个DataSet内部被当做一个数据流。上面的概念既适用于批处理程序,也适用于流处理程序,除了一些例外: