Flink官方文档:https://flink.apache.org/
Flink官方中文文档:https://apachecn.github.io/flink-doc-zh
一.Flink是什么
Flink 主页在其顶部展示了该项目的理念:“Apache Flink 是为分布式、高性能、随时可用以及准确的流处理应用程序打造的开源流处理框架”。
具体来说,Apache Flink 是一个框架和分布式处理引擎,用于对无界和有界数据流进行有状态计算。Flink 被设计在所有常见的集群环境中运行,以内存执行速度和任意规模来执行计算。
Flink的优点有
- 高吞吐和低时延
- 可以连接到常用的外部系统例如MQ、MySQL、Redis、HDFS等
- 高可用
Flink的应用场景
- 例如天猫双11实时交易额数字大屏
- 传感器实时数据采集、展示
- 平台实时检测用户异常行为
二.入门概念
无界数据流:有定义流的开始,但没有定义流的结束。无界数据流会源源不断的产生数据,且无界流的数据需要持续处理。无界流处理通常被称为流处理
有界数据流:有定义流的开始,也有定义流的结束。有界流可以等待所有数据到达后再进行计算,且可以进行排序,有界流处理通常被称为批处理。
DataStream:流处理,用于处理无界数据流。
DataSet:批处理,用于处理有界数据流。
有状态计算:是指在程序计算过程中,在Flink程序内部存储计算产生的中间结果,并提供给后续Function或算子计算结果使用。状态数据可以存储在Flink自己的存储介质中。
三.快速上手案例(单词统计)
需求:统计一段文字中,每个单词出现的频次。
1.创建一个Maven工程。
2.导入Maven坐标。
<properties>
<flink.version>1.17.0</flink.version>
</properties>
<dependencies>
<!--Flink核心依赖-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java</artifactId>
<version>${flink.version}</version>
</dependency>
<!--Flink客户端-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-clients</artifactId>
<version>${flink.version}</version>
</dependency>
</dependencies>
3.批处理(DataSet API 已过时)
批处理基本思路:先逐行读入文件数据,然后将每一行文字拆分成单词;接着按照单词分组,统计每组数据的个数,就是对应单词的频次。
3.1 数据准备
在项目根目录创建 input 文件夹,并在下面创建文件 words.txt ,并输入一些文本。
3.2 代码编写
新建Java类BatchWordCount。
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;
/**
* DataSet API 实现 WordCCount (不推荐)
*/
public class BatchWordCount {
public static void main(String[] args) throws Exception {
// 1. 创建批式执行环境
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
// 2. 从文件读取数据 按行读取(存储的元素就是每行的文本)
DataSource<String> lineDS = env.readTextFile("input/words.txt");
// 3. 转换数据格式
FlatMapOperator<String, Tuple2<String, Long>> wordAndOne = lineDS.flatMap((FlatMapFunction<String, Tuple2<String, Long>>) (line, out) -> {
String[] words = line.split(" ");
for (String word : words) {
// 转换成 二元组 ( word , 1 ) 并通过 采集器 向下游发送数据
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();
}
}
输出结果:
(flink,1)
(world,1)
(hello,3)
(java,1)
4.流处理(有界流)
DataStream API更加强大,可以直接处理批处理和流处理的所有场景,也是官方推荐的方式。
4.1 代码编写
新建Java类StreamWordCount 。
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;
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((FlatMapFunction<String, Tuple2<String, Long>>) (line, out) -> {
String[] words = line.split(" ");
for (String word : words) {
// 转换成 二元组 ( word , 1 ) 并通过 采集器 向下游发送数据
out.collect(Tuple2.of(word, 1L));
}
}).keyBy(data -> data.f0).sum(1);
// 4.打印
sum.print();
// 5.执行
env.execute();
}
}
以上代码会报错
这是因为Flink还具有一个类型提取系统,可以分析函数的输入和返回类型,自动获取类型信息,从而获得对应的序列化器和反序列化器。但是,由于Java中泛型擦除的存在,在某些特殊情况下(比如Lambda表达式中),自动提取的信息是不够精细的——只告诉Flink当前的元素由“船头、船身、船尾”构成,根本无法重建出“大船”的模样;这时就需要显式地提供类型信息,才能使应用程序正常工作或提高其性能。
因为对于flatMap里传入的Lambda表达式,系统只能推断出返回的是Tuple2类型,而无法得到Tuple2<String, Long>。只有显式地告诉系统当前的返回类型,才能正确地解析出完整数据。
则需要改成以下,或者使用匿名内部类实现。
import org.apache.flink.api.common.functions.FlatMapFunction;
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;
/**
* DataStream API 实现 WordCCount
*/
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((FlatMapFunction<String, Tuple2<String, Long>>) (line, 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();
}
}
输出结果:
2> (java,1)
7> (flink,1)
3> (hello,1)
3> (hello,2)
3> (hello,3)
5> (world,1)
通过结果也能体现“流”的概念,数据来一条处理一条,hello这个单词累加的过程,从2到3,就是有状态计算的体现。其中结果前的数字是并行线程编号。
主要观察与批处理程序BatchWordCount的不同:
- 创建执行环境的不同,流处理程序使用的是StreamExecutionEnvironment。
- 转换处理之后,得到的数据对象类型不同。
- 分组操作调用的是keyBy方法,可以传入一个匿名函数作为键选择器(KeySelector),指定当前分组的key是什么。
- 代码末尾需要调用env的execute方法,开始执行任务。
5.流处理( Socket 无界流)
在实际的生产环境中,真正的数据流其实是无界的,有开始却没有结束,这就要求我们需要持续地处理捕获的数据。为了模拟这种场景,可以监听socket端口,然后向该端口不断的发送数据。
5.1 安装netcat
在服务器上安装netcat,用于开启Socket隧道,让flink连接。
[root@VM-12-13-centos ~]# yum install -y netcat
配置并使环境变量生效
export NETCAT_HOME=/usr/local/netcat
export PATH=$PATH:$NETCAT_HOME/bin
5.2 代码编写
新建Java类SocketStreamWordCount ,代码如下:
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;
/**
* DataStream 实现 wordCount : 读Socket(无界流)
*/
public class SocketStreamWordCount {
public static void main(String[] args) throws Exception {
// 创建流式执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 2. 读取文本流:socketTextStream(hostname,port);
DataStreamSource<String> lineStream = env.socketTextStream("服务器ip", 8877);
System.out.println(lineStream);
// 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();
}
}
启动。
5.3 启动Socket
启动Socket隧道,并设置通信端口8877
[root@VM-12-13-centos ~]# nc -lk 8877
开启 监听模式,用于指定nc将处于监听模式。通常 这样代表着为一个 服务等待客户端来链接指定的端口。(记得开放对应端口)
注意:要先启动端口,后启动StreamWordCount程序,否则会报超时连接异常。
5.4 启动java程序
程序启动之后没有任何输出、也不会退出。这是正常的,因为Flink的流处理是事件驱动的,当前程序会一直处于监听状态,只有接收到数据才会执行任务、输出统计结果。
5.5 发送数据
往命令行输入一些内容,例如:
[root@VM-12-13-centos ~]# nc -lk 8877
hello eason
hello flink
控制台则会输出:
6> (eason,1)
3> (hello,1)
7> (flink,1)
3> (hello,2)
Flink入门就先写到这。