Flink v1.6.1 官方文档学习 —《DataStream API 之概述》

目录

1. 示例程序

2. DataSources

3. DataStream 转换操作

4. Data Sinks

5. 迭代

6. 执行参数

6.1 容错

6.2 控制延迟

7. Debugging

7.1 本地运行环境

7.2 集合数据源

7.3 迭代器Sink


Flink DataStream程序是实现了data streams转换操作(比如过滤、更新状态、定义窗口、聚合等等)的一般程序。data streams最初可以从多种sources创建(消息队列,socket流,文件等)。结果可以通过sink返回。例如写出到文件或者标准输出(例如终端命令行)。Flink程序可以在多种环境下运行,单节点、或嵌入到其他程序。可以在本地JVM或很多机器组成的集群上执行。

关于Flink API的基本概念介绍,请查阅《基本概念》。

为了创建你自己的DataStream程序,我们建议你从Flink程序构成开始,逐步添加流转换操作。

1. 示例程序

下面是一个完整的,可运行的流式窗口word count应用。它计算了5秒窗口内从socket 传来的单词数。

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(0)
      .timeWindow(Time.seconds(5))
      .sum(1)

    counts.print()

    env.execute("Window Stream WordCount")
  }
}

为了运行这个示例,首先需要从终端启动netcat发送数据流:

nc -lk 9999

// 不知道为啥,我的虚拟机上用-lk不行,用的-lp

随便在netcat窗口打几个单词,就会有输入进去到word count程序。如果你想看大于1的单词,就5s内在窗口打下重复相同的单词。(如果你不能打那么快,可以增加窗口时间)

2. DataSources

Sources是你的程序读取输入的地方。你可以使用StreamExecutionEnvironment.addSource(sourceFunction)为你的程序添加一个source。Flink自带了一些已经实现了的source函数,但是你仍然可以通过实现SourceFunction(用于non-parallel source),或者实现ParallelSourceFunction接口、继承RichParallelSourceFunction(适用于RichParallelSourceFunction )来定义自己的Source。

这里有几种预定义的stream soure,可从StreamExecutionEnvironment获得:

基于文件

  • readTextFile(path) - 读取text文件。例如,服从TextInputFormat规范的问津,以字符串的形式,按行返回;
  • readFile(fileInputFormat,path) - 按指定格式读取文件(读取一次);
  • readFile(fileInputFormat,path,watchType,interval,pathFilter,typeInfo) - 前两个方法内部调用的也是这个方法。这个方法按指定文件数据格式读取path路径下的文件。根据watchType,source会阶段性(毫秒单位)的监控这个路径,以获取新数据(FileProcessingMode.PROCESS_CONTINUOUSLY),或者只处理当前路径下有的数据(一次,FileProcessingMode.PROCESS_ONCE),并退出。使用pathFilter,用户可以剔除一些不需要处理的文件。

实现原理:

在底层,Flink将文件读取过程分成两个子任务,也就是目录监控和数据读取。每个子任务都由一个单独的实体实现。监控由一个单独的、非并行(并行度为1)的task实现。而读取由多个task并行执行。后者(也就是读取数据的task)的并行度和job的并行度相同。这个单独的监控task的任务是扫描目录(周期性的,或只有一次,取决于watchType类型),找到需要处理的文件,把它们分片,并把这些分片指派给下游的Reader。reader是读取实际数据的对象。每个分片只能被一个reader读取,但每个reader可以读取多个分片,以一个接一个的方式。

需要注意的两点:

1)如果watchType设置为FileProcessingMode.PROCESS_CONTINUOUSLY,那么当文件有改动时,文件的所有内容都会被重新处理。这会破坏"精确一次语义",因为在文件末尾添加数据会导致文件的所有内容都被处理。

2)FileProcessingMode.PROCESS_ONCE,source只会扫描目录一次,并退出,并不会等待reader读完所有内容。当然,reader会持续读取,直到读完所有内容。关闭source将会导致之后不再有检查点。这可能会导致节点失败后恢复缓慢,因为job会重新读取最后一个检查点的数据。

 基于 socket

 socketTextStream - 从socket读取数据。元素可以诶分隔符分隔。

基于集合

  • fromCollection(collection) - 从Java集合创建data stream。集合内的所有元素必须是同一类型;
  • fromCollection(Iterator, Class)  - 从迭代器创建 data stream。class是迭代器返回元素的数据类型;
  • fromElement(T ...) - 从指定的对象队列创建 data stream。所有的对象必须是同一类型;
  • fromParallelCollection(splittableIterator, Class) - 并行的从迭代器创建 data stream。class是迭代器返回元素的数据类型;
  • generateSequence(from, to) - 并行的根据给定的区间产生数字队列;

自定义

  • addSource - 添加新source的函数。例如,从kafka读取数据,可以使用 addSource(new FlinkKafkaConsumer08<>(...)).更多信息请参阅连接器。

3. DataStream 转换操作

stream转换操作概览请查看操作符。

4. Data Sinks

Data Sink消费数据流,并把数据转发给文件、socket、外部系统,或者打印出来。Flink提供了几种内置的输出格式,并封装到了DataStream的操作中:

  • writeAsText() / TextOutputFormat - 以字符串的形式写出元素。字符串是对每个元素调用toString方法获得的;
  • writeAsCsv(...) / CsvOutputFormat - 将Tuple写出为逗号分隔的值文件。行和字段分隔符都是可配置的。每个字段的值都由toString方法得来;
  • print() / printToErr() - 将每个元素的toString()打印到标准输出/错误流。可以输出前面加前缀,这个是可选的。这可以帮助区分不同的打印结果。如果并行度大于1,输出结果将会加上job的标识符作为前缀。
  • writeToSocket - 根据序列化约束写出元素到socket;
  • addSink - 使用自定义Sink。Flink通过Connector绑定到其他实现为sink功能的系统(例如Kafka)

请注意,DataStream的write*()函数主要是为了debug目的。他们不参与Flink的检查点,也就是说这些函数通常具有"至少一次语义"。哪些数据刷写到目标系统依赖于OutputFormat的具体实现。也就是不是所有发送到OutputFormat的元素都会立即出现在目标系统。同时,在失败的情况下,这些记录可能会丢失。

出于可靠性的考虑,精确一次的传递stream到文件系统,可使用flink-connector-filesystem。同时,自定义的实现可通过.addSource(...)方法参与到Flink的检查点,以实现"精确一次"语义。

5. 迭代

流迭代程序实现了一个step函数,并嵌入到了 IterativeStream。因为流程序永远不会结束,所以迭代的次数也就没有最大值。所以,你需要指定哪部分流需要返回迭代,哪部分流需要使用split或filter向下游转发。这里,我们展示一个迭代示例,其主体部分(重复计算的部分)是一个简单的map操作,转发到下游的数据使用filter过滤。

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 */))
})

例如,这是一个将一系列整数持续减一,直至其小于零的程序

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)
  }
)

.iterate()函数接收的参数是 stepfunction: initialStream => (feedback, output),feedback是要返回去执行迭代的流(示例中的stillGreaterThanZero)output是迭代的输出结果(示例中的lessThanZero)。

6. 执行参数

StreamExecutionEnvironment包含了可以设置运行时环境参数的ExecutionConfig。

大多数的参数解释可在execution configuration找到。下述参数只对DataStream API适用:

  • setAutoWatermarkInterval(long milliseconds):设置自动产生水印的间隔。当前值可通过 getAutoWatermarkInterval()获得。
env.getConfig.setAutoWatermarkInterval(1000)
env.getConfig.getAutoWatermarkInterval

6.1 容错

State和Checkpoint描述了如何启用和配置Flink的检查点机制。

6.2 控制延迟

默认情况下,元素在网络上不是一个一个传输(这会引起不必要的网络阻塞),而是缓存下来的。缓存的大小(实际传输的数据量)可以通过Flink的配置文件设置。尽管这个方法有利于优化吞吐量,但当输入流不够快时可能会引起延迟问题。为控制吞吐量和延迟,你可以使用env.setBufferTimeout(timeoutMillis)来设置等待buffer填满的最大等待时间。到时间后,不管buffer有没有填满都会自动发送,其默认值是100ms.

val env: LocalStreamEnvironment = StreamExecutionEnvironment.createLocalEnvironment
env.setBufferTimeout(timeoutMillis)

env.generateSequence(1,10).map(myMap).setBufferTimeout(timeoutMillis)

为最大化吞吐量,设置 setBufferTimeout(-1),这会移除超时时间,buffer只会在填满后传输。为最小化延迟,可以把超时时间设置接近于0(5或10ms),超时时间设置为0将会被忽略,因为这会引起严重的性能退化。

7. Debugging

在集群上运行流程序之前确保实现的算法是正确的是个不错的主意。

Flink支持在IDE中进行本地debug,这大大方便了数据分析程序的开发。

7.1 本地运行环境

LocalStreamEnvironment会在它被创建的那个JVM中启动Flink系统。如果你时从IDE中启动LocalStreamEnvironment的,你可以在代码中设置断点,轻松的debug你的程序。

val env = StreamExecutionEnvironment.createLocalEnvironment()

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

env.execute()

7.2 集合数据源

为方便测试,Flink提供了由Java集合支持的特殊数据源。一旦测试通过,source和sink可以轻松的替换成外部的数据系统。

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)

注意:当前的集合数据源要求数据类型和迭代器是可序列化的。此外,集合数据源不可以并行执行(并行度为1)。

7.3 迭代器Sink

为了测试,Flink也为集合DataStream结果提供了sink。

mport 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 模块在Flink1.5.0中已经移除,它的类转移到了flink-streaming-javaflink-streaming-scala模块。

你可能感兴趣的:(Flink)