一.背景
在企业数字化转型进入深水区的当下,数据的 “实时性” 与 “精细化流转” 已成为支撑业务创新的核心诉求。MySQL 作为企业核心业务数据库(如交易系统、用户中心、订单管理等),存储着关键业务数据,这些数据的变更(新增、修改、删除)需要被实时捕获、传输,以支撑实时监控、数据同步、实时报表、下游系统联动等场景。然而,传统数据同步方案逐渐难以适配业务对 “低延迟、高可靠、精细化分发” 的核心需求,痛点日益凸显:
传统数据同步多依赖定时全量查询、触发器等方式,存在明显局限:定时查询无法满足实时性要求,数据延迟常达分钟级甚至小时级,难以支撑即时决策类业务;触发器则会侵入业务数据库,增加数据库性能开销,且容错性差,易因数据异常导致同步中断。同时,即便采用 Binlog 解析工具捕获数据变更,传统方案往往将所有表的变更数据汇总写入单一存储或消息队列 Topic,导致下游消费端需花费大量精力筛选目标表数据,数据流转效率低、耦合度高,且难以应对多下游系统差异化的数据消费需求。
在此背景下,“Flink CDC + MySQL Binlog + Kafka 分 Topic” 的协同架构成为解决上述痛点的最优解,各组件各司其职、形成高效闭环:
- MySQL Binlog 作为数据变更源头:Binlog 是 MySQL 自带的二进制日志,完整记录了数据库表的所有变更操作(DML/DDL),是捕获数据实时变更的核心载体,无需侵入业务代码,对原系统性能影响极小;
- Flink CDC 作为实时数据捕获引擎:Flink CDC(Change Data Capture)基于 Flink 流处理框架开发,专门用于实时捕获数据库变更数据,支持 Exactly-Once 语义,具备高吞吐、低延迟、高容错的特性。它可直接解析 MySQL Binlog,无需依赖中间件,能实时提取各表的变更数据,并支持数据清洗、格式转换等基础处理,是连接 MySQL 与 Kafka 的核心桥梁;
- Kafka 按表分 Topic 作为数据分发枢纽:Kafka 凭借高吞吐、低延迟、高可用的特性,成为实时数据流分发的核心组件。按 MySQL 表名拆分不同 Kafka Topic,可实现数据的 “精细化分发”—— 每个表的变更数据独立存储在专属 Topic 中,下游系统(如实时计算引擎、数据仓库、业务应用)可按需订阅目标 Topic,无需筛选无效数据,极大提升消费效率,同时降低系统耦合度,适配多下游差异化需求。
Java 作为 Flink、Kafka 生态的核心开发语言,具备完善的 API 支持、成熟的企业级实践与丰富的生态工具链,能够确保架构的稳定性、可扩展性与可维护性。因此,采用 Java 实现 “Flink CDC 读 MySQL Binlog 按表写入 Kafka 不同 Topic” 的方案,本质是构建一套 “实时变更捕获 - 精细化数据分发 - 高可靠流转” 的端到端数据链路。该方案可广泛应用于实时数据仓库增量同步、业务系统跨库实时联动、实时监控告警、用户行为实时分析等场景,帮助企业打破数据流转的 “实时壁垒” 与 “耦合瓶颈”,实现核心业务数据的高效、精准流转,为数据驱动决策提供实时、可靠的数据支撑。
二.具体实现
1.创建java工程,引入依赖
<dependency>
<groupId>com.ververica</groupId>
<artifactId>flink-connector-mysql-cdc</artifactId>
<version>2.4.2</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-java</artifactId>
<version>1.18.1</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java</artifactId>
<version>1.18.1</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-clients</artifactId>
<version>1.18.1</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka</artifactId>
<version>3.0.2-1.18</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
2.定义cdc 源
MySqlSource<String> sourceFunction = MySqlSource.<String>builder()
.hostname(mysql地址)
.port(mysql端口)
.scanNewlyAddedTableEnabled(true)
.username(mysql用户)
.password(mysql密码)
.databaseList(数据库1,数据库2)
.tableList(数据库1.表1,数据库1.表2... ...)
.deserializer(new JsonDebeziumDeserializationSchema(false, configs))
.startupOptions(startupOptions)
.build();
DataStreamSource<String> streamSource = env.fromSource(sourceFunction, WatermarkStrategy.noWatermarks(), "MySQL Source");
2.转换数据为Tuple2<topic名称, binlog数据>格式流
DataStream<Tuple2<String, JSONObject>> resultStream = streamSource.map(new MapFunction<String, Tuple2<String, JSONObject>>() {
@Override
public Tuple2<String, JSONObject> map(String value) throws Exception {
JSONObject data = JSONObject.parseObject(value);
if(tableTopicMapConfigs.containsKey(data.getJSONObject("source").getString("table"))){
return new Tuple2<>(tableTopicMapConfigs.getString(data.getJSONObject("source").getString("table")), data);
}else{
throw new Exception("未找到对应的topic");
}
}
});
3.定义动态topic序列化结构
public class DynamicTopicsSerializationSchema implements KeyedSerializationSchema<Tuple2<String,JSONObject>> {
@Override
public byte[] serializeKey(Tuple2<String,JSONObject> value) {
String pk = value.f1.hashCode()+"";
return (pk).getBytes();
}
@Override
public byte[] serializeValue(Tuple2<String,JSONObject> value) {
String values = value.f1.toString();
return values.getBytes();
}
@Override
public String getTargetTopic(Tuple2<String,JSONObject> value) {
return value.f0;
}
}
4.定义动态分区
public class DynamicTopicsKafkaPartitioner extends FlinkKafkaPartitioner<Tuple2<String,JSONObject>> {
private static Logger logger = LoggerFactory.getLogger(DynamicTopicsKafkaPartitioner.class);
@Override
public int partition(Tuple2<String,JSONObject> pairValue, byte[] key, byte[] value, String topic, int[] partitions) {
return Math.abs(pairValue.f1.hashCode() % partitions.length);
}
public static void main(String[] args) {
}
}
5.定义kafka sink
Properties producerConfig = new Properties();
producerConfig.setProperty(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG,kafka地址);
producerConfig.setProperty(ProducerConfig.COMPRESSION_TYPE_CONFIG, "zstd");
producerConfig.put("sink.delivery-guarantee","exactly-once");
FlinkKafkaProducer flinkKafkaProducer = new FlinkKafkaProducer(
"",
new DynamicTopicsSerializationSchema(),
producerConfig,
java.util.Optional.of(new DynamicTopicsKafkaPartitioner()),
FlinkKafkaProducer.Semantic.EXACTLY_ONCE,
FlinkKafkaProducer.DEFAULT_KAFKA_PRODUCERS_POOL_SIZE
);
resultStream.addSink(flinkKafkaProducer);
1万+

被折叠的 条评论
为什么被折叠?



