1.什么是DataStream?
DataStream是Flink中可以在数据流的基础上实现各种transformation操作的程序,(比如filtering,updating state,defining windows,aggregating)。这些数据流最初的来源可以有很多种,比如消息队列,socket流,文件等,计算的结果通过sinks途径返回,你也可以写这些数据到一个文件或者标准的输出系统中(比如命令行控制台)。Flink程序可以运行在一个文件(本地测试),standalone模式或者嵌入其他程序等多种方式,Flink程序的执行发生在一个local JVM或者一个集群的许多机器上。
在介绍DataStream API之前,我们先了解一下Flink程序的基本流程。
2.Flink Program
Flink程序像常规的程序一样对数据集合进行转换操作,每个程序由下面几部分组成:
- 获取一个执行环境
- 加载/创建初始化数据
- 指定对于数据的transformations操作
- 指定计算的输出结果(打印或者输出到文件)
- 触发程序执行
StreamExecutionEnvironment 是所有Flink程序的基础,你可以使用StreamExecutionEnvironment类的静态方法来获取,有以下三个静态方法:
getExecutionEnvironment()
createLocalEnvironment()
createRemoteEnvironment(String host, int port, String... jarFiles)
典型地,当你依赖于文本实现一些计算时,你需要使用getExecutionEnvironment()来获取;如果执行程序在IDE或者java程序中时可以创建一个本地环境变量来执行触发程序;如果需要打包程序jar包运行,可以使用命令行提交,如下命令所示:
./bin/flink run ./examples/batch/WordCount.jar
可以通过执行环境来指定数据源,有多种数据源可以指定,比如命令行,CSV文件读取,使用消息队列消费等格式,比如读取一个文本文件的数据:
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<String> text = env.readTextFile("file:///path/to/file");
接着你可以使用DataStream的transformation方法来对数据进行操作,比如下面的实例,将源数据转换为Integer格式数据:
DataStream<String> input = ...;
DataStream<Integer> parsed = input.map(new MapFunction<String, Integer>() {
@Override
public Integer map(String value) {
return Integer.parseInt(value);
}
});
接着你可以将计算的结果打印在控制台或者写入某个文件,比如下面的写法:
writeAsText(String path)
print()
最后,我们需要使用运行环境变量Env调用execute方法来触发Flink程序的执行。
下面通过一个简单的WordCount程序来看看Flink程序的基本写法与执行结果,如果你使用的IDE来编写程序,需要导入下面maven依赖,Flink版本使用的是最新版本1.6.1。
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-scala_2.11</artifactId>
<version>${flink.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-scala_2.11</artifactId>
<version>${flink.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-core</artifactId>
<version>${flink.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-clients_2.11</artifactId>
<version>${flink.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka-0.10_2.11</artifactId>
<version>${flink.version}</version>
</dependency>
程序使用Scala编写(会scala的话java照着翻译就行,很简单),WordCount程序如下所示:
import org.apache.flink.api.scala._
import org.apache.flink.core.fs.FileSystem.WriteMode
/**
* 可以直接本地运行
*/
object WordCount {
def main(args: Array[String]) {
val env = ExecutionEnvironment.createLocalEnvironment(1)
//从本地读取文件
val text = env.readTextFile("F:\\FlinkProject\\src\\main\\scala\\cn\\just\\shinelon\\data\\word.txt")
//单词统计
val counts = text.flatMap { _.toLowerCase.split("\\W+") filter { _.nonEmpty } }
.map { (_, 1) }
.groupBy(0) //按照第一个字段分组
.sum(1) //第二个字段求和
//输出结果
counts.print()
//保存结果到txt文件
counts.writeAsText("F:\\FlinkProject\\src\\main\\scala\\cn\\just\\shinelon\\print\\wordcount.txt", WriteMode.OVERWRITE)
env.execute("Scala WordCount Example")
}
}
程序执行结果如下所示:
3.数据源
3.1文本文件
你可以绑定一个数据源到你的程序使用StreamExecutionEnvironment.addSource(sourceFunction)。
下面有几种数据源的输入格式API可以使用StreamExecutionEnvironment来调用:
readTextFile(path):读取一个文本文件,输入文件可以指定输入格式
readFile(fileInputFormat, path):从指定目录读取文件
readFile(fileInputFormat, path, watchType, interval, pathFilter, typeInfo):这个方法在指定文件格式的基础上读取文件,依赖于watchType,它可以通过执行interval(ms为单位)爱周期性的监控目录下产生的新的数据。主要有两种文件处理模式:(FileProcessingMode.PROCESS_CONTINUOUSLY)模式和(FileProcessingMode.PROCESS_ONCE)
注意:
- 如果watchType设置为FileProcessingMode.PROCESS_CONTINUOUSLY,当一个文件被修改,它的整个文本内容将会全部被重新执行,这样会破坏"exactly-once"语义(不懂的可以查一下,也可以参考我的手记【Spark Streaming容错性分析】)。当在文件末尾增加新的数据时整个文件都将会被重新处理一次。
- 如果watchType设置为FileProcessingMode.PROCESS_ONCE,它将会扫描已经存在在路径中的文件,不会等等待reader区完成读取文件内容,不过reader会一直读取数据直到读完整个文件。
3.2 socket流数据
socketTextStream :从socket读取数据,元素之间按照指定分隔符分割
3.3 集合数据
主要有以下API:
fromCollection(Collection):创建一个数据流从Java.util.Collection,集合中的所有元素必须指定相同的类型
fromCollection(Iterator, Class):使用iterator创建一个数据流,通过class指定iterator元素的数据类型
fromElements(T ...):使用对象队列来创建数据流,所有的对象必须有相同的类型。
fromParallelCollection(SplittableIterator, Class) :使用并行方式的iterator
generateSequence(from, to) :生成一个指定范围的数字队列
3.4 Consumer
addSource :绑定一个kafka Consumer,比如addSource(new FlinkKafkaConsumer08<>(...))
4. DataStream Transformations
详情可以查看文档transformation operations。
5. Data Sink
主要有以下API:
writeAsText() / TextOutputFormat:写入一个文本文件
writeAsCsv(...) / CsvOutputFormat:写入一个CSV格式文件
print() / printToErr():输出打印到控制台
writeUsingOutputFormat() / FileOutputFormat :支持对象到字节的转换
writeToSocket:写一个元素到socket根据SerializationSchema
addSink:调用一个custom sink方法,也可以绑定其他系统,比如kafka
6. Iteratoins
迭代流程序实现了一个步进函数并且嵌入它到一个IterativeStream中,一个DataStream程序也许永远也不会执行完毕,它没有迭代的最大次数,不过,你必须使用一个split transformation或者filter来反馈到iteration函数,可以通过下面方式创建使用:
IterativeStream<Integer> iteration = input.iterate();
DataStream<Integer> iterationBody = iteration.map(/* this is executed many times */);
下面通过一个简单的案例来说明:
import org.apache.flink.streaming.api.scala._
/**
* 测试迭代流程序
* 0-1000的每一个整数迭代减1
*/
object SubtractIterator {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
val someIntegers = env.generateSequence(0,1000)
val iteratedStream = someIntegers.iterate(iteration =>{
//map transformation
val minusOne = iteration.map(x=>x-1)
//filter
val stillGreaterThanZero = minusOne.filter (_ > 0)
val lessThanZero = minusOne.filter(_ <= 0)
(stillGreaterThanZero, lessThanZero)
})
iteratedStream.print()
env.execute("SubtractIterator")
}
}
7.执行参数
StreamExecutionEnvironment 的方法ExecutionConfig 允许设置程序运行时参数。比如:
setAutoWatermarkInterval(long milliseconds):设置发射流自动标记间隔,也可以获取当前值使用方法long getAutoWatermarkInterval()
8.容错机制
Flink可以使用state&Checkpoint机制进行容错,可以参考文档State&CheckPoint 不过还有一些隐患需要注意,默认的,元素不能在网络之间一个一个的进行传输,但是它可以通过buffer的形式,buffer的大小可以通过Flink的配置文件设置,我们可以设置最佳的数据吞吐量,但是它也有可能会造成一个隐患是输入流不是足够快,为了协调吞吐量和这个隐患,我们可以使用env.setBufferTimeout(timeoutMillis) 来设置一个最大等待时间来等待buffer填满数据,如果等待超时,buffer会自动发送事件即使buffer还没有填满,默认的超时时间是100ms。例如:
LocalStreamEnvironment env = StreamExecutionEnvironment.createLocalEnvironment();
env.setBufferTimeout(timeoutMillis);
env.generateSequence(1,10).map(new MyMapper()).setBufferTimeout(timeoutMillis);
为了设置最大的吞吐量,你可以设置setBufferTimeout(-1)来取消超时等待只有当buffer填满了才发送,当然你也可以设置为0来避免上述隐患,加快输入流的速度,不过这样会导致性能严重下降。