Flink DataStream API编程指南

Flink中的DataStream程序是对数据流实现转换的常规程序(如过滤、更新状态、定义窗口、聚合)。数据流最初是由各种来源(如消息队列、套接字流、文件)创建的。结果通过汇流返回,例如可以将数据写入文件,或标准输出(例如命令行终端)。Flink程序可以在各种环境下运行,独立运行,或者嵌入到其他程序中。执行可以发生在本地JVM中,也可以发生在许多机器的集群中。

为了创建你自己的Flink DataStream程序,我们鼓励你从一个Flink程序的解剖学开始,并逐步添加你自己的流转换。其余部分作为额外操作和高级功能的参考。

什么是DataStream?

DataStream API的名称来自于特殊的DataStream类,它用于表示Flink程序中的数据集合。你可以把它们看作是不可改变的数据集合,可以包含重复的数据。这些数据既可以是有限的,也可以是无边界的,你用来处理它们的API是一样的。

DataStream在用法上与普通的Java Collection类似,但在一些关键方面却有很大不同。它们是不可改变的,这意味着一旦它们被创建,你就不能添加或删除元素。你也不能简单地检查里面的元素,而只能使用DataStream API操作对它们进行操作,这也被称为转换。

你可以通过在Flink程序中添加一个源来创建一个初始的DataStream。然后你可以从中派生新的流,并通过使用API方法,如map、filter等来组合它们。

Flink程序的解剖

Flink程序看起来就像转换DataStreams的普通程序。每个程序都由相同的基本部分组成。

  1. 获取一个执行环境。
  2. 加载/创建初始数据。
  3. 指定该数据的变换。
  4. 指定你的计算结果放在哪里。
  5. 触发程序执行

现在我们将对其中的每一个步骤进行概述,更多细节请参考相关章节。注意,Scala DataStream API的所有核心类都可以在org.apache.flink.stream.api.scala中找到。

StreamExecutionEnvironment是所有Flink程序的基础。你可以使用StreamExecutionEnvironment上的这些静态方法获得一个。

getExecutionEnvironment()

createLocalEnvironment()

createRemoteEnvironment(host: String, port: Int, jarFiles: String*)

  

通常情况下,你只需要使用getExecutionEnvironment(),因为这将根据上下文做正确的事情:如果你在IDE里面执行你的程序,或者作为一个普通的Java程序,它将创建一个本地环境,在你的本地机器上执行你的程序。如果你从你的程序中创建了一个JAR文件,并通过命令行调用它,Flink集群管理器将执行你的主方法,并且getExecutionEnvironment()将返回一个在集群上执行你的程序的执行环境。

对于指定数据源,执行环境有几种方法可以使用不同的方法从文件中读取数据:你可以只是逐行读取,作为CSV文件,或者使用任何其他提供的数据源。如果只是将文本文件作为一个行的序列来读取,你可以使用。

val env = StreamExecutionEnvironment.getExecutionEnvironment()

val text: DataStream[String] = env.readTextFile("file:///path/to/file")

  

这将为您提供一个DataStream,然后您可以在其上应用变换来创建新的派生DataStream。

你可以通过调用DataStream上的方法和转换函数来应用转换。例如,一个地图转换看起来像这样。

val input: DataSet[String] = ...

val mapped = input.map { x => x.toInt }

  

这将通过将原始集合中的每一个字符串转换为一个Integer来创建一个新的DataStream。

一旦你有了一个包含最终结果的DataStream,你就可以通过创建一个水槽将其写入外部系统。这些只是创建水槽的一些示例方法。

writeAsText(path: String)

print()

  

一旦你指定了完整的程序,你需要通过调用StreamExecutionEnvironment上的execution()来触发程序的执行。根据ExecutionEnvironment的类型,将在你的本地机器上触发执行,或者将你的程序提交到集群上执行。
execute()方法将等待作业完成,然后返回一个JobExecutionResult,这个包含执行时间和累加器结果。
如果你不想等待作业完成,你可以在StreamExecutionEnvironment上调用executeAysnc()来触发异步作业执行。它将返回一个JobClient,你可以用它与刚刚提交的作业进行通信。例如,下面是如何通过使用executeAsync()来实现execute()的语义。

final JobClient jobClient = env.executeAsync();

final JobExecutionResult jobExecutionResult = jobClient.getJobExecutionResult().get();

  

最后这部分关于程序执行的内容对于理解Flink操作何时以及如何执行至关重要。所有的Flink程序都是懒惰地执行的。当程序的主方法被执行时,数据加载和转换不会直接发生。相反,每个操作都被创建并添加到一个数据流图中。当执行环境上的execute()调用明确触发执行时,这些操作才会被实际执行。程序是在本地执行还是在集群上执行,取决于执行环境的类型

懒惰评估可以让你构建复杂的程序,Flink作为一个整体规划的单元来执行。

程序示例

下面的程序是一个完整的,工作的流媒体窗口单词计数应用程序的例子,它可以在5秒的窗口中计算来自网络插座的单词。你可以复制和粘贴代码在本地运行它。

import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.time.Time object WindowWordCount {
def main(args: Array[String]) { val env = StreamExecutionEnvironment.getExecutionEnvironment
val text = env.socketTextStream("localhost", 9999) val counts = text.flatMap { _.toLowerCase.split("\\W+") filter { _.nonEmpty } }
.map { (_, 1) }
.keyBy(_._1)
.window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
.sum(1) counts.print() env.execute("Window Stream WordCount")
}
}

  

要运行示例程序,先用netcat从终端启动输入流。

nc -lk 9999

  

只需输入一些单词,按回车键输入一个新单词。这些词将被输入到单词计数程序中。如果你想看到大于1的计数,请在5秒内反复输入同一个单词(如果你不能那么快打字,请从5秒开始增加窗口大小)。

数据来源

源是你的程序读取其输入的地方。你可以通过使用StreamExecutionEnvironment.addSource(sourceFunction)将一个源附加到你的程序中。Flink提供了许多预先实现的源函数,但是你可以通过实现非并行源的SourceFunction,或者实现并行源的ParallelSourceFunction接口或扩展RichParallelSourceFunction来编写自己的自定义源。

有几种预定义的流源可以从StreamExecutionEnvironment中访问。

  • 基于文件的

readTextFile(path) - 逐行读取文本文件,即尊重TextInputFormat规范的文件,并将其作为字符串返回。

readFile(fileInputFormat, path) - 根据指定的文件输入格式读取(一次)文件。

readFile(fileInputFormat, path, watchType, interval, pathFilter) - 这是前面两个方法内部调用的方法。它根据给定的fileInputFormat读取路径中的文件。根据所提供的watchType,这个源可能会周期性地监视(每间隔ms)路径中的新数据(FileProcessingMode.PROCESS_CONTINUOUSLY),或者处理一次当前路径中的数据并退出(FileProcessingMode.PROCESS_ONCE)。使用pathFilter,用户可以进一步排除被处理的文件。

实施

在外壳下,Flink将文件读取过程分成两个子任务,即目录监控和数据读取。这些子任务中的每一个都是由一个单独的实体实现的。监控由一个单一的、非并行(并行度=1)的任务实现,而读取则由多个任务并行运行。后者的并行度等于任务的并行度。单个监控任务的作用是扫描目录(根据watchType的不同,定期或只扫描一次),找到要处理的文件,将其分割,并将这些分割的文件分配给下游的阅读器。读取器是那些将读取实际数据的人。每个分片只能由一个读取器读取,而一个读取器可以读取多个分片,一个接一个。

重要提示

如果watchType被设置为FileProcessingMode.PROCESS_CONTINUOUSLY,当一个文件被修改时,它的内容会被完全重新处理。这可能会打破 "精确地一次 "的语义,因为在文件末尾追加数据会导致其所有内容被重新处理。

如果watchType被设置为FileProcessingMode.PROCESS_ONCE,那么源码就会对路径扫描一次并退出,而不会等待读者完成对文件内容的读取。当然,读取器会继续读取,直到读取完所有文件内容。关闭源码会导致在这之后不再有检查点。这可能会导致节点故障后的恢复速度变慢,因为作业将从最后一个检查点开始恢复读取。

  • 基于Socket的

socketTextStream - 从套接字读取。元素可以用定界符分开。

  • 基于集合的

fromCollection(Seq) - 从Java Java.util.Collection中创建数据流。集合中的所有元素必须是相同的类型。

fromCollection(Iterator) - 从迭代器中创建一个数据流。该类指定迭代器返回的元素的数据类型。

fromElements(elements: _*) - 从给定的对象序列中创建一个数据流。所有对象必须是相同的类型。

fromParallelCollection(SplittableIterator) - 从迭代器中并行创建数据流。该类指定了迭代器返回的元素的数据类型。

generateSequence(from, to) - 在给定的区间内并行生成数字序列。

  • 自定义的

addSource - 附加一个新的源函数。例如,要从Apache Kafka读取数据,你可以使用addSource(new FlinkKafkaConsumer<>(...))。更多细节请参见连接器。

数据流转换

请参阅操作员,了解可用的流转换的概述。

数据汇

数据汇消耗DataStreams,并将其转发到文件、套接字、外部系统或打印。Flink有各种内置的输出格式,这些格式被封装在DataStreams的操作后面。

  • writeAsText() / TextOutputFormat - 将元素逐行写成字符串。Strings是通过调用每个元素的toString()方法获得的。
  • writeAsCsv(...) / CsvOutputFormat - 将元组写成逗号分隔的值文件。行和字段定界符是可配置的。每个字段的值来自对象的toString()方法。
  • print() / printToErr() - 将每个元素的toString()值打印在标准输出/标准错误流上。可以选择提供一个前缀(msg),这个前缀被添加到输出中。这可以帮助区分不同的 print 调用。如果并行度大于1,输出也将被预置为产生输出的任务的标识符。
  • writeUsingOutputFormat() / FileOutputFormat - 用于自定义文件输出的方法和基类。支持自定义对象到字节的转换。
  • writeToSocket - 根据SerializationSchema将元素写入socket。
  • addSink - 调用一个自定义的水槽函数。Flink捆绑了连接其他系统(如Apache Kafka)的连接器,这些连接器被实现为sink函数。

请注意,DataStream上的write*()方法主要是为了调试的目的。它们不参与Flink的检查点,这意味着这些函数通常具有最多一次的语义。数据冲洗到目标系统取决于OutputFormat的实现。这意味着并非所有发送到OutputFormat的元素都会立即在目标系统中显示出来。另外,在失败的情况下,这些记录可能会丢失。

为了可靠地、精确地一次性将流传送到文件系统中,请使用StreamingFileSink。另外,通过.addSink(...)方法的自定义实现可以参与Flink对精确只读语义的检查点。

迭代

迭代流程序实现了一个步骤函数,并将其嵌入到IterativeStream中。由于DataStream程序可能永远不会结束,所以没有最大的迭代次数。相反,你需要指定流的哪一部分被反馈到迭代中,哪一部分使用侧输出或过滤器转发到下游。在这里,我们展示了一个迭代的例子,其中主体(重复计算的部分)是一个简单的映射变换,而反馈回来的元素是通过使用过滤器转发到下游的元素来区分的。

val iteratedStream = someDataStream.iterate(
iteration => {
val iterationBody = iteration.map(/* this is executed many times */)
(iterationBody.filter(/* one part of the stream */), iterationBody.filter(/* some other part of the stream */))
})

  

例如,这里的程序是从一系列整数中连续减去1,直到它们达到零。

val someIntegers: DataStream[Long] = env.generateSequence(0, 1000)

val iteratedStream = someIntegers.iterate(
iteration => {
val minusOne = iteration.map( v => v - 1)
val stillGreaterThanZero = minusOne.filter (_ > 0)
val lessThanZero = minusOne.filter(_ <= 0)
(stillGreaterThanZero, lessThanZero)
}
)

  

执行参数

StreamExecutionEnvironment包含ExecutionConfig,它允许为运行时设置作业的特定配置值。

请参考执行配置,了解大多数参数的解释。这些参数专门与DataStream API有关。

setAutoWatermarkInterval(long milliseconds): 设置自动发射水印的时间间隔。你可以通过long getAutoWatermarkInterval()来获取当前值。

容错能力

状态和检查点介绍了如何启用和配置Flink的检查点机制。

控制时延

默认情况下,元素不会在网络上逐一传输(会造成不必要的网络流量),而是进行缓冲。缓冲区(实际在机器之间传输)的大小可以在Flink配置文件中设置。虽然这种方法有利于优化吞吐量,但当传入的数据流速度不够快时,会造成延迟问题。为了控制吞吐量和延迟,你可以在执行环境上(或者在单个操作者上)使用env.setBufferTimeout(timeoutMillis)来设置缓冲区填满的最大等待时间。过了这个时间,即使缓冲区没有满,也会自动发送。这个超时的默认值是100毫秒。

使用方法:

val env: LocalStreamEnvironment = StreamExecutionEnvironment.createLocalEnvironment
env.setBufferTimeout(timeoutMillis) env.generateSequence(1,10).map(myMap).setBufferTimeout(timeoutMillis)

  

为了最大限度地提高吞吐量,设置setBufferTimeout(-1),这将消除超时,缓冲区只有在满时才会被刷新。为了最大限度地减少延迟,将超时设置为接近0的值(例如5或10毫秒)。应避免缓冲区超时为0,因为它会导致严重的性能下降。

调试

在分布式集群中运行一个流程序之前,最好先确保实现的算法能够按照预期的方式运行。因此,实现数据分析程序通常是一个检查结果、调试和改进的渐进过程。

Flink提供了一些功能,通过支持IDE内的本地调试、测试数据的注入和结果数据的收集,大大简化了数据分析程序的开发过程。本节给出一些提示,如何简化Flink程序的开发。

本地执行环境

LocalStreamEnvironment在它创建的同一个JVM进程中启动Flink系统。如果你从IDE中启动LocalEnvironment,你可以在代码中设置断点,轻松调试你的程序。

LocalEnvironment的创建和使用方法如下。

val env = StreamExecutionEnvironment.createLocalEnvironment()

val lines = env.addSource(/* some source */)
// build your program env.execute()

  

收集数据来源

Flink提供了特殊的数据源,这些数据源由Java集合支持,以方便测试。一旦程序被测试,源和汇就可以很容易地被从外部系统读取/写入的源和汇所替代。

集合数据源的使用方法如下。

val env = StreamExecutionEnvironment.createLocalEnvironment()

// Create a DataStream from a list of elements
val myInts = env.fromElements(1, 2, 3, 4, 5) // Create a DataStream from any Collection
val data: Seq[(String, Int)] = ...
val myTuples = env.fromCollection(data) // Create a DataStream from an Iterator
val longIt: Iterator[Long] = ...
val myLongs = env.fromCollection(longIt)

  

注:目前,集合数据源要求数据类型和迭代器实现Serializable。此外,集合数据源不能并行执行( parallelism = 1)。

迭代器数据汇聚

Flink还提供了一个收集DataStream结果的汇,用于测试和调试目的。它的使用方法如下。

import org.apache.flink.streaming.experimental.DataStreamUtils
import scala.collection.JavaConverters.asScalaIteratorConverter val myResult: DataStream[(String, Int)] = ...
val myOutput: Iterator[(String, Int)] = DataStreamUtils.collect(myResult.javaStream).asScala

  

注意:flink-streaming-contrib模块从Flink 1.5.0中移除。它的类被移到了flink-streaming-java和flink-streaming-scala中。

下一步去哪里?

Flink-v1.12官方网站翻译-P016-Flink DataStream API Programming Guide的更多相关文章

  1. Flink-v1.12官方网站翻译-P005-Learn Flink: Hands-on Training

    学习Flink:实践培训 本次培训的目标和范围 本培训介绍了Apache Flink,包括足够的内容让你开始编写可扩展的流式ETL,分析和事件驱动的应用程序,同时省略了很多(最终重要的)细节.本书的重 ...

  2. Flink-v1.12官方网站翻译-P025-Queryable State Beta

    可查询的状态 注意:可查询状态的客户端API目前处于不断发展的状态,对所提供接口的稳定性不做保证.在即将到来的Flink版本中,客户端的API很可能会有突破性的变化. 简而言之,该功能将Flink的托 ...

  3. Flink-v1.12官方网站翻译-P002-Fraud Detection with the DataStream API

    使用DataStream API进行欺诈检测 Apache Flink提供了一个DataStream API,用于构建强大的.有状态的流式应用.它提供了对状态和时间的精细控制,这使得高级事件驱动系统的 ...

  4. Flink-v1.12官方网站翻译-P015-Glossary

    术语表 Flink Application Cluster Flink应用集群是一个专用的Flink集群,它只执行一个Flink应用的Flink作业.Flink集群的寿命与Flink应用的寿命绑定. ...

  5. Flink-v1.12官方网站翻译-P022-Working with State

    有状态程序 在本节中,您将了解Flink为编写有状态程序提供的API.请看一下Stateful Stream Processing来了解有状态流处理背后的概念. 带键值的数据流 如果要使用键控状态,首 ...

  6. Flink-v1.12官方网站翻译-P017-Execution Mode (Batch/Streaming)

    执行模式(批处理/流处理) DataStream API 支持不同的运行时执行模式,您可以根据用例的要求和作业的特点从中选择.DataStream API 有一种 "经典 "的执行 ...

  7. Flink-v1.12官方网站翻译-P014-Flink Architecture

    Flink架构 Flink是一个分布式系统,为了执行流式应用,需要对计算资源进行有效的分配和管理.它集成了所有常见的集群资源管理器,如Hadoop YARN.Apache Mesos和Kubernet ...

  8. Flink-v1.12官方网站翻译-P013-Timely Stream Processing

    及时的流处理 介绍 及时流处理是有状态流处理的一种扩展,其中时间在计算中起着一定的作用.其中,当你做时间序列分析时,当做基于某些时间段(通常称为窗口)的聚合时,或者当你做事件处理时,事件发生的时间很重 ...

  9. Flink-v1.12官方网站翻译-P011-Concepts-Overview

    概念-概览 实践培训解释了作为Flink的API基础的有状态和及时流处理的基本概念,并提供了这些机制如何在应用中使用的例子.有状态的流处理是在数据管道和ETL的背景下介绍的,并在容错部分进一步发展.在 ...

随机推荐

  1. Rejecting mapping update to [xxx] as the final mapping would have more than 1 type: [xxx, xx]

    说明: 1.elasticsearch 版本 6.3.1 2.在同一个index下创建两个type时报错,信息如下: 在创建第二个type:solr时,先前已经在相同索引下创建了一个type:es [ ...

  2. idea中maven的安装与配置

    说明:类似maven安装和配置的帖子在网上有很多,本人也有做过参照,但是有些帖子的步骤跳跃性比较大,故此,本人整理了一下,给大家做个参考. 一.下载安装 一般都是在官网进行下载 https://mav ...

  3. MySQL 标识符到底区分大小写么——官方文档告诉你

    最近在阿里云服务器上部署一个自己写的小 demo 时遇到一点问题,查看 Tomcat 日志后定位到问题出现在与数据库服务器交互的地方,执行 SQL 语句时会返回 指定列.指定名 不存在的错误.多方查证 ...

  4. 第1章 什么是JavaScript

    目录 1. JavaScript实现 1.1 ECMAScript 1.2 DOM 1.3 BOM 1995年JavaScript问世时主要用途时代替Perl等服务器段语言处理输入验证 1. Java ...

  5. TCP/IP五层模型概述

    • 为什么要分层?    ○ 协议太多,将众多协议分层解决,能提高效率,复杂问题简单化,更容易发现问题,并针对性解决问题.• OSI七层模型     ○ 同层使用相同的协议,下层为上层提供服务.   ...

  6. layui城市三级联动(fesiong / layarea)

    安装 GitHub下载地址:https://github.com/fesiong/layarea.git 使用(html+js) 1. html部分 整个选择器需要使用一个父标签包裹,如下使用了id= ...

  7. JavaCV更新到1.5.x版本后的依赖问题说明以及如何精简依赖包大小

    javaCV全系列文章汇总整理 javacv教程文档手册开发指南汇总篇 前言 JavaCV更新到1.5.x版本,依赖包也迎来了很大变化,体积也变大了不少.很多小伙伴们反馈,之前很多1.3.x和1.4. ...

  8. LeetCode733 图像渲染

    有一幅以二维整数数组表示的图画,每一个整数表示该图画的像素值大小,数值在 0 到 65535 之间. 给你一个坐标 (sr, sc) 表示图像渲染开始的像素值(行 ,列)和一个新的颜色值 newCol ...

  9. 【Flutter】容器类组件之变换

    前言 Transform可以在其子组件绘制时对其应用一些矩阵变换来实现一些特效. 接口描述 const Transform({ Key key, @required this.transform, t ...

  10. java8新特性之stream流

    Stream 流是 Java 8 提供给开发者一套新的处理集合的API,他把我们将要处理的集合作为流,就像流水线一样,我们可以对其中的元素进行筛选,过滤,排序等中间操作,只不过这种操作更加简洁高效. ...