前面我们尝试用flink写了一个WordCount程序,并且介绍了flink提供的api层级,接下来我们正式来学习flink的DataStream API。
为什么先学习DataStream API,而不是ProcessFunction API,这是因为ProcessFunction API是通过DataStream API调用的,它作为DataStream API的补充,可以实现DataStream API所不能实现的一些高级功能。
什么是DataStream
在开始介绍DataStream的API之前,我们先来看一下什么是DataStream。
以flink程序角度来讲,DataStream,如其中文名,数据流,是一个特殊的类,这个类表示了数据的集合,并且数据是不可变的,但可以是重复的。而对于数据流来说,我们前面说过,它可以是有界的也可以是无界的,可以对DataStream进行各种转换得到不同的DataStream。
我们可以这样类比,把DataStream理解成一个溪流,数据是溪流里面的水:
- 流水是无法变更的,流过去就流过去了,即表示数据是不可变的。
- 溪流弯弯曲曲的流向远方,有千转百折,即是我们定义在DataStream上相关的转换。
- 溪流的水可以一直流,这是无界的,也可以流了之后断掉,就是有界的。
DataStream程序结构剖析
我们再回到WordCount的那个例子(只保留了代码主体部分):
// 1. 获取流任务执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 2. 输入,这里使用了自带的socket输入,监听ip地址为hadoop001,端口号为9999
DataStream<String> source = env.socketTextStream("hadoop001", 9999);
// 3. 数据转换处理
DataStream<Tuple2<String, Integer>> counted = source
.map(xxx)
.keyBy(xxx)
.sum(1)
;
// 4. 输出,这里使用了自带的控制台输出,将结果输出到控制台
counted .print("output");
// 5. 执行流任务
env.execute("word count test");
DataStream API程序跟很多数据处理程序一样,主要分为:
- 数据输入
- 数据转化处理
- 数据输出
在执行程序之前,我们要获取到执行上下文环境,所以DataStream API中先要创建执行环境。
而flink的执行方式是惰式执行(即先定义数据处理计划,最后再执行),并且对于流式数据而言,都是数据(事件)驱动,只能事先定义好数据处理逻辑,所以最后还要加一个启动命令告诉flink开始执行job。
最终我们可以得到一个flink DataStream API程序由以下5部分组成:
- 获取流任务执行环境
- 加载/创建数据源
- 指定数据转换方式
- 指定数据处理结果要输出到什么地方
- 触发程序执行
获取流任务执行环境
我们可以通过以下方式来获取执行环境:
1. getExecutionEnvironment()
2. createLocalEnvironment()
3. createRemoteEnvironment(String host, int port, String... jarFiles)
不过,我们一般只用到getExecutionEnvironment
方法,因为它会智能地根据上下文创建合适的执行环境:
- 如果我们在IDE或将它作为一个普通的java程序中执行,它会创建一个本地环境并且在本机上执行我们的程序。
- 如果我们创建了一个jar包,并且通过命令行的方式启动,那么flink集群管理器会执行主程序并且
getExecutionEnvironment()
会返回一个适合该集群的执行环境。
任务执行环境对象也提供了api方便我们设置一些参数:
// 执行参数配置
ExecutionConfig executionConfig= env.getConfig();
// checkpoint相关参数配置
CheckpointConfig checkpointConfig = env.getCheckpointConfig();
现在我们无需了解,相关配置等用到的时候再学习。
加载/创建数据源
数据源(Data Sources)是我们程序读取数据的来源,来源的方式可以有很多种:文件系统、网络io、数据库、消息队列等,同时也可以来自于内存(从内存创建数据源)。只要能提供数据的都可以作为数据源,比如我们从文件里面读数据:
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<String> text = env.readTextFile("file:///path/to/file");
而前文WordCount例子中,我们读取的是来自于网络socket的数据:
DataStream<String> source = env.socketTextStream("hadoop001", 9999);
从内存中读取可以这样写:
List<String> word = new ArrayList<>();
word.add("hello");
word.add("world");
word.add("flink");
DataStream<String> flintstones = env.fromCollection(word);
当然了,在实际应用程序中,我们很少这样写,更多使用的是低延迟,高吞吐的可重放数据源,如kafka等。
上面举的几个例子都是flink中已经实现好的读取数据源的方式,我们可以使用StreamExecutionEnvironment.addSource(sourceFunction)
方法,通过实现SourceFunction
接口来自定义数据源。
指定数据转换方式
数据转换方式就是实现我们数据处理逻辑的地方。我们可以把输入的DataStream经过一系列的数据转换变成新的DataStream。我们可以直接在DataStream对象后面直接跟数据转换方法,如下面的map
操作:
DataStream<String> input = ...;
DataStream<Integer> parsed = input.map(new MapFunction<String, Integer>() {
@Override
public Integer map(String value) {
return Integer.parseInt(value);
}
});
这步map
操作将String
转换为了Integer
,并且以此创建了一个新的DataStream。数据转换方式有很多,具体的api我们后面再详细介绍学习。
指定数据处理结果要输出到什么地方
数据经过一系列的转换之后得到了我们需要的结果,那么这个时候我们可以通过数据汇(Data Sinks)就要将它输出到指定的地方,如数据库中,供后续的查询使用。数据可以简单地写出到文件或控制台中:
writeAsText(String path) // 将数据以文本方式写出到文件中
print() // 将数据打印到控制台
这些方法是flink内置的数据写出方法,我们也可以自定义数据汇写出到我们感兴趣的地方:
env.addSink(SinkFunction);
触发程序执行
最后我们通过调用execute()
方法来执行程序。根据我们的执行环境的不同,它会将程序提交到不同的地方执行。如果是本地环境,则在本机上执行;如果是集群环境,则提交到相应的集群上执行。通常我们需要给execute()
方法传递一个String
对象,即为job名称,以方便我们查看,如果不指定,程序会默认使用Flink Streaming Job
作为job名称,如:
execute("My First Flink Streaming Job")
需要注意的是,execute()
是个同步方法,它会一直等待到job执行结束,然后返回一个JobExecutionResult
对象,在这个对象中包含了job执行时间和聚合结果等等,一般我们使用execute()
方法已经足够了。不过如果你不想等到job结束,就去做其他的事情,flink也提供了一个异步方法:executeAsync()
。它会返回一个JobClient
对象,这个对象可以与我们刚刚提交的job进行交互,并获取结果:
final JobClient jobClient = env.executeAsync();
final JobExecutionResult jobExecutionResult = jobClient.getJobExecutionResult().get();
不过这个不怎么常用,知道一下就好。
flink程序都是惰式执行的,所有的数据加载、数据转换操作和数据写出操作都不会立即执行。在flink学习(四)——flink任务执行原理详解中,我们提到了flink会根据用户的api生成StreamGraph,而这一步就是在execute()
方法里面完成的。数据执行图构建好之后,程序就会根据执行环境实际地在本机或集群上运行起来。
DataStream API总结
对于日常开发来说,我们使用最多的是数据转换的API,因为flink已经内置了很多常见的Data Sources和Data Sinks。而窗口计算、带时间编程、有状态编程都是在数据转换api中,这块将是我们接下来学习的重点。