第4章 Flink 基础API 二:原算子(source)

300 阅读3分钟

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 控制台结果如下:

source_collection_terminal.png

4.2.2、基于文件的source

真正的实际应用中,自然不会直接将数据写在代码中。通常情况下,我们会从存储介质中获取数据,一个比较常见的方式就是读取日志文件。这也是批处理中最常见的读取方式。

Flink 的的一些读取文件的API 已经过时,我们看 readFile() 底层调用 addSource(...)。 Flink 在 Flink 1.12 从零实现了新的基础框架,在 Flink 1.13 中 kafka、hive 和 file source 已移植到新架构。

read_file_api_old.png

新的读取文件方式需要添加文件连接器依赖

        <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;

kafka_connector_1.png

4.2.5、自定义 source

自定义的 source 内容较多,后面单独写一篇“自定义 source - Mysql”来说明怎么自己定义 connector 并在项目中使用。