4.2、源算子(source)
Flink 可以从各种来源获取数据,然后构建 DataStream 进行转换处理。一般将数据的输入来源称为数据源(data source),而读取数据的算子就是源算子(source operator)。所以 source 就是我们整个处理程序的输入端。
根据数据的来源,source 可以分为 5 类:
- 基于集合的 source;
- 基于文件的 source;
- 基于 Socket 网络端口的 source;
- 基于第三方的 Connector source
- 自定义 source。
从并行度的角度,source 又可以分为非并行的 source 和并行的 source。
- 非并行 source
并行度只能为 1,即只有一个运行时实例,在读取大量数据时效率比较低,通常是用来做一些实验或测试,例如 Socket Source;
- 并行 Source
并行度可以是 1 到多个,在计算资源足够的前提下,并行度越大,效率越高。例如Kafka Source;
在 Flink1.12 以前,旧的添加 source 的方式,是调用执行环境的 addSource()方法:
DataStream stream = env.addSource(...);
方法传入的参数是一个“源函数”(source function),需要实现 SourceFunction 接口。
从 Flink1.12 开始,主要使用流批统一的新 Source 架构:
DataStreamSource stream = env.fromSource(…)
Flink 直接提供了很多预实现的接口,此外还有很多外部连接工具也帮我们实现了对应的 Source,通常情况下足以应对我们的实际需求。
4.2.1、基于集合的 source
可将一个普通的 Java 集合、迭代器或者可变参数转换成一个分布式数据流DataStream。基于集合的 source 一般用于学习测试时编造数据,主要有下面几个 API:
- env.fromElements(可变参数);
非并行的 Source,可以将一到多个数据作为可变参数传入到该方法中,返回DataStreamSource。
- env.fromColletion(各种集合);
非并行的 Source,可以将一个 Collection 作为参数传入到该方法中,返回一个DataStreamSource.
- env.generateSequence(开始,结束); 已过时
并行的 Source(并行度也可以通过调用该方法后,再调用 setParallelism 来设置)通过指定的起始值和结束值来生成数据序列流;
- env.fromSequence(开始,结束);
并行的 Source(并行度也可以通过调用该方法后,再调用 setParallelism 来设置)通过指定的起始值和结束值来生成数据序列流;
具体代码
package org.mochi.datastream;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import java.util.Arrays;
public class SourceCollection {
public static void main(String[] args) throws Exception {
// 1、创建流式处理环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 2、创建基于集合的 source
// env.fromElements(可变参数);
DataStreamSource<String> ds1 = env.fromElements("spark", "flink");
// env.fromCollection(各种集合);
//
DataStreamSource<String> ds2 = env.fromCollection(Arrays.asList("spark", "flink"));
// env.generateSequence(开始,结束);@deprecated
DataStreamSource<Long> ds3 = env.generateSequence(1, 3);
// env.fromSequence(开始,结束)
DataStreamSource<Long> ds4 = env.fromSequence(5, 7).setParallelism(2);
// 3、transformation
// 4、sink
ds1.print();
ds2.print();
ds3.print();
ds4.print();
// 5、execute
env.execute();
}
}
IDE 控制台结果如下:
4.2.2、基于文件的source
真正的实际应用中,自然不会直接将数据写在代码中。通常情况下,我们会从存储介质中获取数据,一个比较常见的方式就是读取日志文件。这也是批处理中最常见的读取方式。
Flink 的的一些读取文件的API 已经过时,我们看 readFile() 底层调用 addSource(...)。 Flink 在 Flink 1.12 从零实现了新的基础框架,在 Flink 1.13 中 kafka、hive 和 file source 已移植到新架构。
新的读取文件方式需要添加文件连接器依赖
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-files</artifactId>
<version>${flink.version}</version>
</dependency>
具体代码
package org.mochi.datastream;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.connector.file.src.FileSource;
import org.apache.flink.connector.file.src.reader.TextLineInputFormat;
import org.apache.flink.core.fs.Path;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
public class SourceFile {
public static void main(String[] args) throws Exception {
// 1、创建流式处理环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// // 过时的方法读取文件
// DataStreamSource<String> ds = env.readTextFile("input/words.txt");
// ds.print();
FileSource<String> fileSource =
FileSource.forRecordStreamFormat(new TextLineInputFormat(), new
Path("input/words.txt")).build();
env.fromSource(fileSource, WatermarkStrategy.noWatermarks(),"file")
.print();
env.execute();
}
}
path 可以有多种方式
- 可以是文件的相对路径或者绝对路径,idea 下是 project 的根目录,standalone 模 式下是集群节点根目录;
- 也可以是目录,此时读取的是该目录下的所有文件内容,比如可以是
input/ - 可以从 HDFS 目录下读取,比如
hdfs://node1:8234//wordcount/input/words.txt"
4.2.3、基于socket 的source
我们之前用到的读取 socket 文本流,就是流处理场景。但是这种方式由于吞吐量小、稳 定性较差,一般也是用于测试。
DataStream stream = env.socketTextStream("127.0.0.1", 9999);
4.2.4、基于 kafka 的 source
Flink 官方提供了连接工具第三方的 connector,我们以生产环境中常用的 flink-connector-kafka 举例,它直接帮我们实现了一个消费者 FlinkKafkaConsumer,它就是用来读取 Kafka 数据的 SourceFunction。 所以想要以 Kafka 作为数据源获取数据,我们只需要引入 Kafka 连接器的依赖。Flink 官方提供的是一个通用的 Kafka连接器,它会自动跟踪最新版本的 Kafka客户端。目前最新版本只支持 0.10.0 版本以上的 Kafka。需要导入的依赖如下:
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka</artifactId>
<version>${flink.version}</version>
</dependency>
具体代码
package org.mochi.source;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.connector.kafka.source.KafkaSource;
import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
public class SourceKafka {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
KafkaSource<String> kafkaSource =
KafkaSource.<String>builder()
.setBootstrapServers("localhost:9092")
.setTopics("topic_1")
.setGroupId("mochi")
.setStartingOffsets(OffsetsInitializer.latest())
.setValueOnlyDeserializer(new SimpleStringSchema())
.build();
DataStreamSource<String> stream = env.fromSource(kafkaSource,
WatermarkStrategy.noWatermarks(), "kafka-source");
stream.print("Kafka");
env.execute();
}
}
这是新的 API ,flink 会把 kafka 消费者的消费位移记录在算子状态中,这样就实现了消费位移状态的容错,从而可以支持端到端的exactly-once;
4.2.5、自定义 source
自定义的 source 内容较多,后面单独写一篇“自定义 source - Mysql”来说明怎么自己定义 connector 并在项目中使用。