目录
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程序构成开始,逐步添加流转换操作。
下面是一个完整的,可运行的流式窗口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内在窗口打下重复相同的单词。(如果你不能打那么快,可以增加窗口时间)
Sources是你的程序读取输入的地方。你可以使用StreamExecutionEnvironment.addSource(sourceFunction)为你的程序添加一个source。Flink自带了一些已经实现了的source函数,但是你仍然可以通过实现SourceFunction(用于non-parallel source),或者实现ParallelSourceFunction接口、继承RichParallelSourceFunction(适用于RichParallelSourceFunction
)来定义自己的Source。
这里有几种预定义的stream soure,可从StreamExecutionEnvironment获得:
基于文件
实现原理:
在底层,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读取数据。元素可以诶分隔符分隔。
基于集合
自定义
stream转换操作概览请查看操作符。
Data Sink消费数据流,并把数据转发给文件、socket、外部系统,或者打印出来。Flink提供了几种内置的输出格式,并封装到了DataStream的操作中:
请注意,DataStream的write*()函数主要是为了debug目的。他们不参与Flink的检查点,也就是说这些函数通常具有"至少一次语义"。哪些数据刷写到目标系统依赖于OutputFormat的具体实现。也就是不是所有发送到OutputFormat的元素都会立即出现在目标系统。同时,在失败的情况下,这些记录可能会丢失。
出于可靠性的考虑,精确一次的传递stream到文件系统,可使用flink-connector-filesystem。同时,自定义的实现可通过.addSource(...)方法参与到Flink的检查点,以实现"精确一次"语义。
流迭代程序实现了一个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)。
StreamExecutionEnvironment包含了可以设置运行时环境参数的ExecutionConfig。
大多数的参数解释可在execution configuration找到。下述参数只对DataStream API适用:
env.getConfig.setAutoWatermarkInterval(1000)
env.getConfig.getAutoWatermarkInterval
State和Checkpoint描述了如何启用和配置Flink的检查点机制。
默认情况下,元素在网络上不是一个一个传输(这会引起不必要的网络阻塞),而是缓存下来的。缓存的大小(实际传输的数据量)可以通过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将会被忽略,因为这会引起严重的性能退化。
在集群上运行流程序之前确保实现的算法是正确的是个不错的主意。
Flink支持在IDE中进行本地debug,这大大方便了数据分析程序的开发。
LocalStreamEnvironment会在它被创建的那个JVM中启动Flink系统。如果你时从IDE中启动LocalStreamEnvironment的,你可以在代码中设置断点,轻松的debug你的程序。
val env = StreamExecutionEnvironment.createLocalEnvironment()
val lines = env.addSource(/* some source */)
// build your program
env.execute()
为方便测试,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)。
为了测试,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-java
和 flink-streaming-scala模块。