2.1、创建项目
在准备好所有的开发环境之后,我们就可以开始开发自己的第一个 Flink 程序了。首先我们要做的,就是在 IDEA 中搭建一个 Flink 项目的框架。我们会使用 Java 项目中常见的 Maven 来进行依赖管理。
2.1.1、创建工程
打开 IntelliJ IDEA,创建一个 Maven 工程
2.1.2、添加项目依赖
在项目的 pom 文件中,添加 Flink 的依赖,包括 flink-java、flink-streaming-java 以及 flink-clients
</properties>
<flink.version>1.16.2</flink.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-clients</artifactId>
<version>${flink.version}</version>
</dependency>
</dependencies>
2.2、WordCount 代码编写
需求: 统计一段文字中,每个单词出现的频次
2.2.1、编程模版
无论简单与复杂,Flink 程序都由如下几个部分组成
- 获取一个编程、执行入口环境 env ;
- 通过数据源组件、加载、创建 datastream 或 datasource;
- 对 datastream 或 datasource 调用各种处理算子表达计算逻辑 ;
- 通过 sink 算子指定计算结果的输出方式;
- 在 env 上触发程序提交运行。
2.2.2、数据准备
在项目根目录下创建一个input 文件夹,并在下面创建文本文件 words.txt,并输入一些文字,例如:
run flink
run java
run scala
2.2.3、批处理
package org.mochi.wc;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.api.java.operators.AggregateOperator;
import org.apache.flink.api.java.operators.DataSource;
import org.apache.flink.api.java.operators.FlatMapOperator;
import org.apache.flink.api.java.operators.UnsortedGrouping;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.util.Collector;
public class BatchWordCount {
public static void main(String[] args) throws Exception {
// 1、获取执行环境 env
ExecutionEnvironment env =
ExecutionEnvironment.getExecutionEnvironment();
// 2、从文件读取数据 按行读取(存储的元素就是每行的文本)
DataSource<String> lineDS =
env.readTextFile("input/words.txt");
// 3、转换数据格式
FlatMapOperator<String, Tuple2<String, Long>> wordAndOne =
lineDS.flatMap(new FlatMapFunction<String, Tuple2<String, Long>>() {
@Override
public void flatMap(String line, Collector<Tuple2<String,
Long>> out) throws Exception {
String[] words = line.split(" ");
for (String word : words) {
out.collect(Tuple2.of(word,1L));
}
}
});
// 4、按照 word 进行分组
UnsortedGrouping<Tuple2<String, Long>> wordAndOneUG =
wordAndOne.groupBy(0);
// 5、分组内聚合统计
AggregateOperator<Tuple2<String, Long>> sum =
wordAndOneUG.sum(1);
// 6、打印结果
sum.print();
}
}
输出结果
(scala,1)
(flink,1)
(run,3)
(java,1)
这种代码的实现方式,是基于 DataSet API 的,也就把数据当作整个数据集来处理转换的。不过Flink 本身是流批统一的处理架构,所以从 Flink 1.12 开始,官方推荐的做法 是直接使用 DataStream API。如果想继续执行批处理的话,那么就在提交任务时通过将执行模式设为 BATCH: $ bin/flink run -Dexecution.runtime-mode=BATCH BatchWordCount.jar。 这样在实际中只要维护一套 DataStream API 就可 以了。
2.2.4、流处理
对于 Flink 而言,流才是整个处理逻辑的底层核心,所以流批统一之后的 DataStream API 更加强大,可以直接处理批处理和流处理的所有场景。 Flink可以对不同类型的输入数据源进行流处理。
2.2.4.1、读取文件
流处理的整体思路与批处理非常类似,代码模式也基本一致
package org.mochi.wc;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;
public class StreamWordCount {
public static void main(String[] args) throws Exception {
// 1、 创建流式执行环境
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
// 2、 读取文件
DataStreamSource<String> lineStream =
env.readTextFile("input/words.txt");
// 3、 转换、分组、求和、得到统计结果
SingleOutputStreamOperator<Tuple2<String, Long>> sum =
lineStream.flatMap(new FlatMapFunction<String, Tuple2<String,
Long>>() {
@Override
public void flatMap(String line, Collector<Tuple2<String,
Long>> out) throws Exception {
String[] words = line.split(" ");
for (String word : words) {
out.collect(Tuple2.of(word, 1L));
}
}
}).keyBy(data -> data.f0)
.sum(1);
// 4、 打印
sum.print();
// 5、 执行
env.execute();
}
}
输出结果
1> (run,1)
1> (scala,1)
1> (run,2)
1> (run,3)
7> (flink,1)
2> (java,1)
观察与批处理程序 BatchWordCount 的不同:
- 创建执行环境的不同,流处理程序使用的是
StreamExecutionEnvironment; - 转换处理之后,得到的数据对象类型不同;
- 分组操作调用的是
keyBy方法 ,可以传入一个匿名函数作为键选择器(KeySelector),指定当前分组的 key 是什么; - 代码末尾需要调用 env 的
execute方法,开始执行任务。
DataStream 的抽象
- DataStream 代表一个数据流,它可以是无界的,也可以是有界的;
- DataStream 类似于 spark 的 rdd,它是不可变的(immutable);
- 无法对一个 datastream 进行自由的添加或删除或修改元素;
- 只能通过算子对 datastream 中的数据进行转换,将一个 datastream 转成另一个datastream;
- datastream 可以通过 source 算子加载、映射外部数据而来,或者从已存在的datastream转换而来。
Flink 还具有一个类型提取系统,可以分析函数的输入和返回类型,自动获取类型信息, 从而获得对应的序列化器和反序列化器。但是,由于 Java 中泛型擦除的存在,在某些特殊情况下(比如 Lambda表达式中),自动提取的信息是不够精细的,这时就需要显式地提供类型信息,才能使应用程序正常工作或提高其性能。 因为对于 flatMap 里传入的 Lambda 表达式,系统只能推断出返回的是 Tuple2 类型,而无 法得到 Tuple2。只有显式地告诉系统当前的返回类型,才能正确地解析出完整数据。
2.2.4.2、读取 socket 文本流
package org.mochi.wc;
import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;
public class SocketStreamWordCount {
public static void main(String[] args) throws Exception {
// 1、创建流式执行环境
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
// 2、读取文本流:第一个参数表示发送端主机名、第二个参数表示端口号
DataStreamSource<String> lineStream =
env.socketTextStream("127.0.0.1", 9999);
// 3、转换、分组、求和,得到统计结果
SingleOutputStreamOperator<Tuple2<String, Long>> sum =
lineStream.flatMap((String line, Collector<Tuple2<String, Long>> out)
-> {
String[] words = line.split(" ");
for (String word : words) {
out.collect(Tuple2.of(word, 1L));
}
}).returns(Types.TUPLE(Types.STRING, Types.LONG))
.keyBy(data -> data.f0)
.sum(1);
// 4、打印
sum.print();
// 5、执行
env.execute();
}
}
在 Linux 环境的主机上,执行下列命令,发送数据进行测试:nc -lk 9999
mochicruise@mochidembp ~ % nc -lk 9999
要先启动端口,后启动 SocketStreamWordCount 程序,否则会报超时连接异常
启动 SocketStreamWordCount 程序时我们会发现程序启动之后没有任何输出、也不会退出。这是正常的,因为 Flink 的流处理是事件驱动的,当前程序会一直处于监听状态,只有接收到数据才会执行任务。
我们在 Linux 主机上发送数据,在主机上输入 “cat file1”,则在应用程序控制台输出如下内容:
1> (cat,1)
1> (file1,1)
在主机上再输入 “cat file2”,则在应用程序控制台输出如下内容:
8> (file2,1)
1> (cat,2)