执行模式(流/批)
DataStream API 支持不同的运行时执行模式,你可以根据你的用例需要和作业特点进行选择。
DataStream API 有一种”经典“的执行行为,我们称之为流(STREAMING)执行模式。这种模式适用于需要连续增量处理,而且预计无限期保持在线的无边界作业。
此外,还有一种批式执行模式,我们称之为批(BATCH)执行模式。这种执行作业的方式更容易让人联想到批处理框架,比如 MapReduce。这种执行模式适用于有一个已知的固定输入,而且不会连续运行的有边界作业。
Apache Flink 对流处理和批处理统一方法,意味着无论配置何种执行模式,在有界输入上执行的 DataStream 应用都会产生相同的最终 结果。重要的是要注意最终 在这里是什么意思:一个在流模式执行的作业可能会产生增量更新(想想数据库中的插入(upsert)操作),而批作业只在最后产生一个最终结果。尽管计算方法不同,只要呈现方式得当,最终结果会是相同的。
通过启用批执行,我们允许 Flink 应用只有在我们知道输入是有边界的时侯才会使用到的额外的优化。例如,可以使用不同的关联(join)/ 聚合(aggregation)策略,允许实现更高效的任务调度和故障恢复行为的不同 shuffle。下面我们将介绍一些执行行为的细节。
什么时候可以/应该使用批处理模式?
批执行模式只能用于 有边界 的作业/Flink 程序。边界是数据源的一个属性,告诉我们在执行前,来自该数据源的所有输入是否都是已知的,或者是否会有新的数据出现,可能是无限的。而对一个作业来说,如果它的所有源都是有边界的,则它就是有边界的,否则就是无边界的。
而流执行模式,既可用于有边界任务,也可用于无边界任务。
一般来说,在你的程序是有边界的时候,你应该使用批执行模式,因为这样做会更高效。当你的程序是无边界的时候,你必须使用流执行模式,因为只有这种模式足够通用,能够处理连续的数据流。
一个明显的例外是当你想使用一个有边界作业去自展一些作业状态,并将状态使用在之后的无边界作业的时候。例如,通过流模式运行一个有边界作业,取一个 savepoint,然后在一个无边界作业上恢复这个 savepoint。这是一个非常特殊的用例,当我们允许将 savepoint 作为批执行作业的附加输出时,这个用例可能很快就会过时。
另一个你可能会使用流模式运行有边界作业的情况是当你为最终会在无边界数据源写测试代码的时候。对于测试来说,在这些情况下使用有边界数据源可能更自然。
配置批执行模式
执行模式可以通过 execute.runtime-mode 设置来配置。有三种可选的值:
- STREAMING: 经典 DataStream 执行模式(默认)
- BATCH: 在 DataStream API 上进行批量式执行
- AUTOMATIC: 让系统根据数据源的边界性来决定
这可以通过 bin/flink run ... 的命令行参数进行配置,或者在创建/配置 StreamExecutionEnvironment 时写进程序。
下面是如何通过命令行配置执行模式:
$ bin/flink run -Dexecution.runtime-mode=BATCH examples/streaming/WordCount.jar
这个例子展示了如何在代码中配置执行模式:
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setRuntimeMode(RuntimeExecutionMode.BATCH);
我们不建议用户在程序中设置运行模式,而是在提交应用程序时使用命令行进行设置。保持应用程序代码的免配置可以让程序更加灵活,因为同一个应用程序可能在任何执行模式下执行。
统计单词案例
以批处理方式进行统计
-
流程
-
核心代码
ParameterTool parameterFromArgs = ParameterTool.fromArgs(args);
String input = parameterFromArgs.getRequired("input");
// 初始化环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setRuntimeMode(RuntimeExecutionMode.BATCH);
// 加载数据源
DataStreamSource<String> wordSource = env.readTextFile(input, "UTF-8");
// 数据转换
SingleOutputStreamOperator<Word> wordStreamOperator = wordSource.flatMap(new TokenizerFunction());
// 按单词分组
KeyedStream<Word, String> wordKeyedStream = wordStreamOperator.keyBy(new KeySelector<Word, String>() {
@Override
public String getKey(Word word) throws Exception {
return word.getWord();
}
});
// 求和
SingleOutputStreamOperator<Word> sumStream = wordKeyedStream.sum("frequency");
sumStream.print();
env.execute("WordCountBatch");
- 在IDE中运行时,需指定-input参数,输入文件地址
以流处理方式进行统计
-
流程
-
核心代码
// 初始化环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 定义kafka数据源
KafkaSource<String> source = KafkaSource.<String>builder()
.setBootstrapServers("192.168.0.192:9092")
.setTopics("TOPIC_WORD")
.setGroupId("TEST_GROUP")
.setStartingOffsets(OffsetsInitializer.latest())
.setValueOnlyDeserializer(new SimpleStringSchema())
.build();
// 加载数据源
DataStreamSource<String> kafkaWordSource = env.fromSource(source, WatermarkStrategy.noWatermarks(), "Kafka Word Source");
// 数据转换
SingleOutputStreamOperator<Word> wordStreamOperator = kafkaWordSource.flatMap(new TokenizerFunction());
// 按单词分组
KeyedStream<Word, String> wordKeyedStream = wordStreamOperator.keyBy(new KeySelector<Word, String>() {
@Override
public String getKey(Word word) throws Exception {
return word.getWord();
}
});
// 求和
SingleOutputStreamOperator<Word> sumStream = wordKeyedStream.sum("frequency");
sumStream.print();
env.execute("WordCountStream");
完整代码地址
https://github.com/Mr-LuXiaoHua/study-flink
com.example.datastream.wordcount.DataStreamApiWordCountBatch --从文件读取数据进行单词统计
com.example.datastream.wordcount.DataStreamApiWordCountStream --从Kafka消费数据进行单词统计
提交到flink集群执行:
bin/flink run -m 127.0.0.1:8081 -c com.example.datastream.wordcount.DataStreamApiWordCountBatch -input /mnt/data/words.txt /opt/apps/study-flink-1.0.jar
-input 指定输入文件路径