Flink - DataStream Connectors

699 阅读4分钟

官网关于 DataStream Connectors 的根目录为:Overview | Apache Flink

概述

Flink 内置了一些基本的数据源和接收器。 预定义的数据源包括从文件、目录和 Socket 中读取,以及从集合和迭代器中摄取数据。 预定义的数据接收器支持写入文件、stdout 和 stderr 以及 Socket。

连接器提供与各种第三方系统接口的代码。 目前支持以下系统:

请记住,要在应用程序中使用这些连接器之一,通常需要额外的第三方组件,例如 数据存储或消息队列的服务器.

Flink 的其他流连接器正在通过 Apache Bahir发布,包括:

主要使用的 kafka 的 connector 是需要特别掌握的。

JDBC Connector

此连接器提供将数据写入 JDBC 数据库的接收器。

要使用它,请将以下依赖项添加到您的项目(以及您的 JDBC 驱动程序):

<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-connector-jdbc_2.11</artifactId>
    <version>1.13.2</version>
</dependency>

示例:

package com.learn.flink.connectors;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.connector.jdbc.JdbcConnectionOptions;
import org.apache.flink.connector.jdbc.JdbcSink;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.DataStreamSink;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

public class JDBCConnectorDemo {

    public static void main(String[] args) throws Exception {
        //0: env
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);
        //1: source
        final DataStream<Student> ds = env.fromElements(new Student("006", "wangwu5", 20));
        //2: transformation
        //3: sink
        ds.addSink(JdbcSink.sink(
                "INSERT INTO `t_student`(`id`, `name`, `age`) VALUES (?, ?, ?);",

                (ps, student) -> {
                    ps.setString(1, student.getId());
                    ps.setString(2, student.getName());
                    ps.setInt(3, student.getAge());
                },

                new JdbcConnectionOptions.JdbcConnectionOptionsBuilder()
                        .withUrl("jdbc:mysql://node01:3306/bigdata")
                        .withUsername("root")
                        .withPassword("root")
                        .build()

        ));
        //4: execute
        env.execute();
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Student {
        private String id;
        private String name;
        private Integer age;
    }
}

Apache Kafka Connector

Apache Flink 附带了一个通用的 Kafka 连接器,它试图跟踪最新版本的 Kafka 客户端。 它使用的客户端版本可能会在 Flink 版本之间发生变化。 现代 Kafka 客户端向后兼容代理版本 0.10.0 或更高版本。 关于 Kafka 兼容性的详细信息,请参考 Kafka 官方文档。

<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-connector-kafka_2.11</artifactId>
    <version>1.13.2</version>
</dependency>

<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-connector-base</artifactId>
    <version>1.13.2</version>
</dependency>

Kafka Consumer/source

image.png

使用 Kafka 配置为 source 的时候,关键是了解参数的设置:

  • 订阅的主题
  • 反序列化规则

image.png

  • 消费者属性 - 集群地址
  • 消费者属性 - 消费者组id(如果不设置,不建议使用,不方便管理)
  • 消费者属性 - offset 重置规则.latest 从最新的消息开始消费,earliest从最早的消息开始消费
  • 消费者属性 - 动态分区检测(当 Kafka 分区数变化时,Flink 可以自动检测到)
  • 如果没有设置 checkpoint, 那么可以设置自动提交 offset, 后续学习了 checkpoint 会把 offset 随着做 checkpoint 的时候提交到 checkpoint 和默认主题中。

image.png 示例:

package com.learn.flink.connectors;

import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer;

import java.util.Properties;

public class KafkaConsumerSource {

    public static void main(String[] args) throws Exception {
        //0: env
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);
        //1: source
        Properties kafkaProps = new Properties();
        kafkaProps.setProperty("bootstrap.servers", "node01:9092"); // 集群地址
        kafkaProps.setProperty("group.id", "flink");
        kafkaProps.setProperty("auto.offset.reset", "latest"); // 有offset就从记录位置开始,没有offset 就从最新的消息开始消费
        kafkaProps.setProperty("flink.partition-discovery.interval-millis", "5000");// 会开启一个后台线程每隔 5 秒检测一下 kafka 的分区情况,实现动态分区检测
        kafkaProps.setProperty("enable.auto.commit", "true");//自动提交(提交到默认主题,后续学习了 checkpoint 随着 checkpoint 存储到 checkpoint 和默认主题中)
        kafkaProps.setProperty("auto.commit.interval.ms", "2000");//自动提交的时间间隔
        final FlinkKafkaConsumer<String> kafkaSource = new FlinkKafkaConsumer<>("flink_kafka", new SimpleStringSchema(), kafkaProps);

        final DataStream<String> ds = env.addSource(kafkaSource);
        //2: transformation
        
        //3: sink
        ds.print();
        //4: execute
        env.execute();
    }

}

结果:

image.png

Kafka Producer/sink

package com.learn.flink.connectors;

import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer;

import java.util.Properties;

/**
 * connector - sink - kafka
 */
public class KafkaSinkDemo {

    public static void main(String[] args) throws Exception {
        //0: env
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);
        //1: source
        final DataStream<String> ds = env.fromElements("flink kafka sink", "hello kafka");
        //2: transformation

        //3: sink
        Properties propsProducer = new Properties();
        propsProducer.setProperty("bootstrap.servers", "node01:9092");
        final FlinkKafkaProducer<String> kafkaSink = new FlinkKafkaProducer<>("flink_kafka2", new SimpleStringSchema(), propsProducer);
        ds.addSink(kafkaSink);
        //4: execute
        env.execute();
    }

}

结果:

image.png

附以上示例使用的 pom 文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.learn</groupId>
    <artifactId>flink-demo1</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <java.version>11</java.version>
        <flink.version>1.13.2</flink.version>
        <mysql.version>8.0.18</mysql.version>
        <lombok.version>1.18.20</lombok.version>
        <slf4j.version>1.7.25</slf4j.version>
    </properties>

    <!-- 指定仓库地址 -->
    <repositories>
        <repository>
            <id>aliyun</id>
            <name>maven-aliyun</name>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        </repository>
    </repositories>

    <!-- 配置依赖 -->
    <dependencies>
        <!-- flink base -->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-clients_2.11</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-java</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-java_2.11</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <!--flink connectors-->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-connector-jdbc_2.11</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-connector-kafka_2.11</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-connector-base</artifactId>
            <version>${flink.version}</version>
        </dependency>

        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <!-- slf4j -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>${slf4j.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-release-plugin</artifactId>
                <version>2.5.3</version>
            </plugin>
            <plugin>
                <artifactId>maven-source-plugin</artifactId>
                <version>3.2.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>jar-no-fork</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

topic 和 partition 动态发现

实际的生产环境中可能有这样一些需求

  • 比如场景一,有一个 Flink 作业需要将五份数据聚合到一起,五份数据对应五个 kafka topic,随着业务增长,新增一类数据,同时新增了一个 kafka topic,如何在不重启作业的情况下作业自动感知新的 topic。
  • 场景二,作业从一个固定的 kafka topic 读数据,开始该 topic 有 10 个 partition,但随着业务的增长数据量变大,需要对 kafka partition 个数进行扩容,由 10 个扩容到 20。该情况下如何在不重启作业情况下动态感知新扩容的 partition ?

针对上面的两种场景,首先需要在构建 FlinkKafkaConsumer 时的 properties 中设置 flink.partition-discovery.interval-millis 参数为非负值,表示开启动态发现 的开关,以及设置的时间间隔。此时 FlinkKafkaConsumer 内部会启动一个单独的线程定期去 kafka 获取最新的 meta 信息。

  • 针对场景一,还需在构建 FlinkKafkaConsumer 时,topic 的描述可以传一个正则表达式描述的 pattern。每次获取最新 kafka meta 时获取正则匹配的最新 topic 列表。
  • 针对场景二,设置前面的动态发现参数,在定期获取 kafka 最新 meta 信息时会匹配新的 partition。为了保证数据的正确性,新发现的 partition 从最早的位置开始读取。

image.png