官网关于 DataStream Connectors 的根目录为:Overview | Apache Flink
概述
Flink 内置了一些基本的数据源和接收器。 预定义的数据源包括从文件、目录和 Socket 中读取,以及从集合和迭代器中摄取数据。 预定义的数据接收器支持写入文件、stdout 和 stderr 以及 Socket。
连接器提供与各种第三方系统接口的代码。 目前支持以下系统:
- Apache Kafka (source/sink)
- Apache Cassandra (sink)
- Amazon Kinesis Streams (source/sink)
- Elasticsearch (sink)
- FileSystem (Hadoop included) - Streaming only (sink)
- FileSystem (Hadoop included) - Streaming and Batch (sink)
- RabbitMQ (source/sink)
- Apache NiFi (source/sink)
- Twitter Streaming API (source)
- Google PubSub (source/sink)
- JDBC (sink)
请记住,要在应用程序中使用这些连接器之一,通常需要额外的第三方组件,例如 数据存储或消息队列的服务器.
Flink 的其他流连接器正在通过 Apache Bahir发布,包括:
- Apache ActiveMQ (source/sink)
- Apache Flume (sink)
- Redis (sink)
- Akka (sink)
- Netty (source)
主要使用的 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
使用 Kafka 配置为 source 的时候,关键是了解参数的设置:
- 订阅的主题
- 反序列化规则
- 消费者属性 - 集群地址
- 消费者属性 - 消费者组id(如果不设置,不建议使用,不方便管理)
- 消费者属性 - offset 重置规则.
latest从最新的消息开始消费,earliest从最早的消息开始消费 - 消费者属性 - 动态分区检测(当 Kafka 分区数变化时,Flink 可以自动检测到)
- 如果没有设置 checkpoint, 那么可以设置自动提交 offset, 后续学习了 checkpoint 会把 offset 随着做 checkpoint 的时候提交到 checkpoint 和默认主题中。
示例:
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();
}
}
结果:
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();
}
}
结果:
附以上示例使用的 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 从最早的位置开始读取。