一. 前言
Flink CDC(Change Data Capture)是 Apache Flink 提供的一个功能强大的组件,用于实时捕获和处理数据库中的数据变更。
官方文档 @ Apache Flink CDC |Apache Flink CDC
- Flink CDC 不止支持一种数据库 ,包括 MySQL , PostgreSQL , MongoDB 等
- Flink CDC 通常是基于 BinLog 来实现的
- Flink CDC 使用时 ,还是要导入对应的包
宏观概念入门 :
- CDC 是一种技术,用来捕获数据变更
- CDC 的运用场景主要包括 : 数据同步 , 数据分发 ,数据集成
- CDC 的实现方式主要包括 : 基于查询(自行去数据源查询) , 基于日志(Binlog)
简单聊一聊趋势 :
常见的方式包括通过 DataX 实现全量同步后 ,再通过 Canal 实现对应的增量同步。这种方式并没有太大的问题 ,性能方面和操作方式都不难,最大的问题可能就是 : 全量和增量需要分开处理
而 Flink 我认为它的最大好处在于可以同时集成增量和全量
,且适配的上下游比较多(生态好,功能多
).
而在我个人的设计中 ,轻量级的 MySQL 数据同步用 Canal 足够了。 而实时计算要求更高的复杂营销,统计我会考虑 Flink。
生态快速一览(数据同步类型) :
- 支持包括 MySQL / Oracle 等常见关系型数据库的同步
- 支持 MongoDB / HDFS / ES / Hbase 等新数据架构的数据同步
- 支持 集成流处理平台 Apache Pulsar / Pravega
- 支持 Kafka, RabbitMQ 等消息推送能力
- 支持 Java / Python / Scala 等不同的语言
当然除了数据同步 ,Flink 还支持机器学习 , CEP 等高级用法
最后的最后聊一下应用场景 :
就像上文说的 ,功能上包括 : 数据同步 , 数据分发 ,数据集成
。 而在实践上面就可以分为 : 报表分析 ,实时大屏 , 数据应用 ,实时营销
等一些具体的场景了
二. 基础使用流程
2.1 使用方式
- 自行安装 Flink 运行环境 ( 参考文档 )
- 准备 Flink Java 项目 ,用于实现 FlinkCDC 代码
- 执行项目 ,同步数据
2.2 关于版本的选择
Flink CDC 最难的点不在代码上, 而在于版本如何确定
。
- 冲突点一 : FlinkCDC 和 Flink 版本不匹配 ,导致 Flink 无法调用到 CDC
- 冲突点二 : 组件驱动无法和组件匹配 ,写入数据 / 读取数据异常
- 冲突点三 : 不同层级之间存在组件的版本冲突
// Flink CDC 参考
- Flink 1.15.x 对应 flink-connector-mysql-cdc 2.4.x
- Flink 1.14.x 对应 flink-connector-mysql-cdc 2.3.x
- Flink 1.13.x 对应 flink-connector-mysql-cdc 2.2.x
- Flink 1.12.x 对应 flink-connector-mysql-cdc 1.4.x
三. 上手流程
3.1 Maven 依赖
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.gang</groupId>
<artifactId>com-ant-example-mysql</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- Flink dependencies -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-core</artifactId>
<version>1.14.2</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-java</artifactId>
<version>1.14.2</version>
</dependency>
<!-- 版本冲突问题 -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-shaded-guava</artifactId>
<version>30.1.1-jre-14.0</version>
</dependency>
<!-- Flink CDC 依赖 -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java_2.12</artifactId>
<version>1.14.2</version>
</dependency>
<!-- MySQL connector -->
<dependency>
<groupId>com.ververica</groupId>
<artifactId>flink-connector-mysql-cdc</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-jdbc_2.12</artifactId>
<version>1.14.2</version>
</dependency>
<!-- 个人项目依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Maven compiler plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!-- Maven shade plugin for creating a fat jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.ant.flink.FlinkMySQLExampleMain</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
核心关注要点 :
- 我这里使用的 Flink 版本是 1.14.2 , 所以我 Java 项目的依赖都是基于该版本的
- flink-core / flink-java 是构建项目的基础依赖 ,按照版本进行导入就行
- flink-shaded-guava 主要是由于
CDC 和 Flink 引用的 GUAVA 版本不一致
- 表现为
ClassNotFoundException = org.apache.flink.shaded.guava18.....ThreadFactoryBuilder
- 具体使用什么版本可以去
Flink 源码里面找对应的版本看
(最准)
- 表现为
- flink-streaming-java 是 Flink Stream 模式的核心依赖 ,CDC 也是基于这个实现的
- flink-connector-mysql-cdc 和 JDBC 就是主要的业务依赖了
- 最后 ,不要忘了把以上的依赖都打到 Jar 里面去哦
3.2 核心逻辑一览
package com.ant.flink;
import com.alibaba.fastjson.JSONObject;
import com.ververica.cdc.connectors.mysql.MySqlSource;
import com.ververica.cdc.connectors.mysql.table.StartupOptions;
import com.ververica.cdc.debezium.DebeziumDeserializationSchema;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.sink.RichSinkFunction;
import org.apache.flink.streaming.api.functions.source.SourceFunction;
import org.apache.flink.util.Collector;
import org.apache.kafka.connect.data.Field;
import org.apache.kafka.connect.data.Schema;
import org.apache.kafka.connect.data.Struct;
import org.apache.kafka.connect.source.SourceRecord;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class FlinkMySQLExampleMain {
/**
* MySQL 数据同步主方法
*/
public static void main(String[] args) throws Exception {
// S1 : 搭建 Flink Stream 核心容器
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// S2 : 构建 Source 连接
SourceFunction<String> sourceFunction = MySqlSource.<String>builder()
.hostname("123.106.145.123")
.port(3306)
.databaseList("ant-test")// 设置要监听的数据库
.tableList("ant-test.ant_user") // 设置要监听的表
.username("root")
.password("123123132")
.deserializer(new JsonDebeziumDeserializationSchema())// 使用 Debezium 反序列化模式
.startupOptions(StartupOptions.initial()) // 设置启动选项,initial 表示从头开始读取
.build();
// S3 : 添加 source 和 Sink
env.addSource(sourceFunction)
.addSink(new ConsoleSink());
// S4 : 执行 Flink 作业
env.execute("MySQL CDC Example");
}
/**
* BinLog 序列化 : 主要用于解析 BinLog
*/
public static class JsonDebeziumDeserializationSchema implements DebeziumDeserializationSchema {
@Override
public void deserialize(SourceRecord sourceRecord, Collector collector) throws Exception {
log.info("S1 : 拉取到 MySQL Binlog,数据信息:" + sourceRecord);
Map<String, Object> exchangeMap = new HashMap<>();
// 拆分 BinLog 数据库及表信息
String topic = sourceRecord.topic();
String[] split = topic.split("[.]");
String database = split[1];
String table = split[2];
log.info("S2 : 拉取到 MySQL Binlog :" + database + "." + table);
exchangeMap.put("database", database);
exchangeMap.put("table", table);
// 拆分 Binlog 数据信息
Struct struct = (Struct) sourceRecord.value();
Struct after = struct.getStruct("after");
if (after != null) {
Schema schema = after.schema();
Map<String, Object> dataMap = new HashMap<>();
for (Field field : schema.fields()) {
dataMap.put(field.name(), after.get(field.name()));
}
exchangeMap.put("data", JSONObject.toJSONString(dataMap));
log.info("S3 : 拉取到 MySQL Binlog 数据体 {}", JSONObject.toJSONString(dataMap));
}
// 传递数据到下游 Sink
collector.collect(JSONObject.toJSONString(exchangeMap));
}
@Override
public TypeInformation<String> getProducedType() {
return BasicTypeInfo.STRING_TYPE_INFO;
}
}
/**
* 写 Log 到控制台
*/
public static class ConsoleSink extends RichSinkFunction<String> {
public void open(Configuration parameters) throws Exception {
}
public void invoke(String value, Context context) throws Exception {
log.info("S4 : 接收到 MySQL 数据信息 {}", value);
JSONObject exchangeMap = JSONObject.parseObject(value);
FlinkSourceDto sourceDto = exchangeMap.getObject(exchangeMap.getString("data"), FlinkSourceDto.class);
log.info("S5 : 接收到对象 {}", sourceDto);
}
}
/**
* 传递 DTO
**/
@Data
public static class FlinkSourceDto {
private Long id;
private Integer userStatus;
private String userName;
}
}
结果展示 :
3.3 写数据到其他的组件中
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
/**
* 写数据到 MySQL
*/
public static class MySQLSink extends RichSinkFunction<String> {
private Connection connection;
private PreparedStatement preparedStatement;
// 数据库连接配置
private final String jdbcUrl = "jdbc:mysql://123.106.145.123:3306/ant-test";
private final String username = "root";
private final String password = "123123123";
private final String insertQuery = "INSERT INTO ant_user_image (user_name) VALUES (?)";
/**
* 初始化数据库连接
*/
public void open(Configuration parameters) throws Exception {
log.info("初始化MySQL连接信息");
super.open(parameters);
Class.forName("com.mysql.cj.jdbc.Driver");
log.info("JDBC Driver 显式加载完成");
connection = DriverManager.getConnection(jdbcUrl, username, password);
preparedStatement = connection.prepareStatement(insertQuery);
}
public void invoke(String value, Context context) throws Exception {
log.info("S4 : 接收到 MySQL 数据信息 {}", value);
JSONObject exchangeMap = JSONObject.parseObject(value);
FlinkSourceDto sourceDto = JSONObject.parseObject(exchangeMap.getString("data"), FlinkSourceDto.class);
log.info("S5 : 接收到对象 {}", sourceDto);
// 设置 SQL 语句中的参数
preparedStatement.setString(1, sourceDto.getUserName());
// 执行插入操作
preparedStatement.executeUpdate();
}
@Override
public void close() throws Exception {
// 关闭资源
if (preparedStatement != null) {
preparedStatement.close();
}
if (connection != null) {
connection.close();
}
super.close();
}
}
总结
- 版本号是最大的问题 ,要找到对应的版本号 ,其他后面东西都很简单
- 其次就是依赖冲突 ,这里可以下源码 ,从源码里面找到对应的依赖 ,解决即可
最后的最后 ❤️❤️❤️👇👇👇
- 👈 欢迎关注 ,超200篇优质文章,未来持续高质量输出 🎉🎉
- 🔥🔥🔥 系列文章集合,高并发,源码应有尽有 👍👍
- 走过路过不要错过 ,知识无价还不收钱 ❗❗