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. hdfs读写删除过程解析

    一.hdfs文件读取过程 hdfs有一个FileSystem实例,客户端通过调用这个实例的open()方法就可以打开系统中希望读取的文件,hdfs通过rpc协议调用Nadmenode获取block的位 ...

  2. DB2在渗透中的应用(转载)

    原文地址:http://drops.wooyun.org/tips/16673 0x00 DB2简介 DB2是IBM公司推出关系型数据库管理系统. 现今DB2主要包含以下三个系列: DB2 for L ...

  3. spark知识点_RDD

    来自官网的Spark Programming Guide,包括个人理解的东西. 这里有一个疑惑点,pyspark是否支持Python内置函数(list.tuple.dictionary相关操作)?思考 ...

  4. MySql中的有条件插入 insert where

    假设现在我们有这样的需求:当数据库中不存在满足条件的记录时,可以插入一条记录,否则程序退出.该怎么实现? 1年以上工作经验的人应该都能立即想到:去检查一下库里有没有记录,没有就插入,有就结束. int ...

  5. 【葵花宝典】All-in-One模式安装KubeSphere

    1.准备 Linux 机器 2.google api受限下载 KubeKey export KKZONE=cn curl -sfL https://get-kk.kubesphere.io | VER ...

  6. 【九阳神功】Nessus 8_VM不限IP及AWVS破解版合体部署

    Nessus 8下载地址: https://moehu-my.sharepoint.com/personal/ximcx_moebi_org/_layouts/15/download.aspx?Sou ...

  7. oracle常用hint添加

    1.视图添加索引 /* Formatted on 2020/1/6 下午 04:46:37 (QP5 v5.163.1008.3004) */ SELECT /*+index(VIEW_NAME.TA ...

  8. Poj-P3468题解【线段树】

    本文为原创,转载请注明:http://www.cnblogs.com/kylewilson/ 题目出处: http://poj.org/problem?id=3468 题目描述: 给N个数A1, A2 ...

  9. 深度学习DeepLearning技术实战(12月18日---21日)

    12月线上课程报名中 深度学习DeepLearning(Python)实战培训班 时间地点: 2020 年 12 月 18 日-2020 年 12 月 21日 (第一天报到 授课三天:提前环境部署 电 ...

  10. uni-app开发经验分享十九: uni-app对接微信小程序直播

    uni-app对接微信小程序直播 1.登录微信小程序后台-点击>设置->第三方设置->添加直播插件 2.添加直播组件后->点击<详情>      记录这两个参数直播 ...