Flink CDC系列之:Kafka Sink 的序列化器PipelineKafkaRecordSerializationSchema
这是一个 Flink CDC Kafka Sink 的序列化器,负责将 Change Data Capture (CDC) 事件序列化为 Kafka 消息。
核心功能
这个类实现了 KafkaRecordSerializationSchema,专门用于将 CDC 事件序列化为 Kafka 的 ProducerRecord。
关键组件
配置参数
- partition: 分区策略(0分区或null)
- keySerialization: Key 序列化器
- valueSerialization: Value 序列化器
- unifiedTopic: 统一主题名称
- addTableToHeaderEnabled: 是否添加表信息到 Header
- customHeaders: 自定义 Header 键值对
- mappingRuleString: 表到主题的映射规则
构造函数逻辑
java
PipelineKafkaRecordSerializationSchema(
PartitionStrategy partitionStrategy, // 分区策略
SerializationSchema<Event> keySerialization, // Key序列化
SerializationSchema<Event> valueSerialization, // Value序列化
String unifiedTopic, // 统一主题
boolean addTableToHeaderEnabled, // 表头启用
String customHeaderString, // 自定义头字符串
String mappingRuleString) // 映射规则
- 解析自定义 Header 字符串(格式:key1:value1;key2:value2)
- 根据分区策略设置分区号
核心方法解析
serialize() - 序列化方法
java
public ProducerRecord<byte[], byte[]> serialize(Event event, KafkaSinkContext context, Long timestamp) {
// 1. 序列化 Key 和 Value
ChangeEvent changeEvent = (ChangeEvent) event;
final byte[] keySerialized = keySerialization.serialize(event);
final byte[] valueSerialized = valueSerialization.serialize(event);
// 2. 跳过 SchemaChangeEvent
if (event instanceof SchemaChangeEvent) {
return null;
}
// 3. 推断主题名称
String topic = inferTopicName(changeEvent.tableId());
// 4. 构建 Record Headers
RecordHeaders recordHeaders = new RecordHeaders();
// 添加表信息到 Header
if (addTableToHeaderEnabled) {
recordHeaders.add(new RecordHeader(NAMESPACE_HEADER_KEY, namespace.getBytes(UTF_8)));
recordHeaders.add(new RecordHeader(SCHEMA_NAME_HEADER_KEY, schemaName.getBytes(UTF_8)));
recordHeaders.add(new RecordHeader(TABLE_NAME_HEADER_KEY, tableName.getBytes(UTF_8)));
}
// 添加自定义 Header
if (!this.customHeaders.isEmpty()) {
for (Map.Entry<String, String> entry : customHeaders.entrySet()) {
recordHeaders.add(new RecordHeader(entry.getKey(), entry.getValue().getBytes(UTF_8)));
}
}
// 5. 创建 ProducerRecord
return new ProducerRecord<>(
topic, partition, null, keySerialized, valueSerialized, recordHeaders);
}
inferTopicName() - 主题推断逻辑
java
private String inferTopicName(TableId tableId) {
return tableIdToTopicCache.computeIfAbsent(tableId, (table -> {
// 优先使用统一主题
if (unifiedTopic != null && !unifiedTopic.isEmpty()) {
return unifiedTopic;
}
// 其次使用映射规则匹配
if (selectorsToTopicMap != null && !selectorsToTopicMap.isEmpty()) {
for (Map.Entry<Selectors, String> entry : selectorsToTopicMap.entrySet()) {
if (entry.getKey().isMatch(tableId)) {
return entry.getValue();
}
}
}
// 默认使用表名作为主题
return table.toString();
}));
}
open() - 初始化方法
java
public void open(SerializationSchema.InitializationContext context, KafkaSinkContext sinkContext) {
// 解析映射规则
this.selectorsToTopicMap = KafkaSinkUtils.parseSelectorsToTopicMap(mappingRuleString);
// 初始化缓存
this.tableIdToTopicCache = new HashMap<>();
// 初始化值序列化器
valueSerialization.open(context);
}
重要特性
主题映射策略(优先级顺序)
- 统一主题: 所有表数据发送到同一个主题
- 规则映射: 根据表选择器规则映射到不同主题
- 表名主题: 默认使用表名作为主题名
Header 信息丰富
- 表结构信息: namespace, schemaName, tableName
- 自定义 Header: 用户定义的任意键值对
- 便于下游处理: 消费者可以根据 Header 识别数据来源
数据过滤
- 跳过 SchemaChangeEvent: 只处理数据变更事件
- 保留 ChangeEvent: 数据增删改事件
性能优化
- 缓存机制: 表名到主题的映射缓存,避免重复计算
懒加载: 在 open 方法中初始化资源
应用场景
这个序列化器主要用于:
- CDC 数据管道: 将数据库变更实时同步到 Kafka
- 多租户架构: 不同表的数据路由到不同主题
- 数据湖入湖: 为下游数据湖提供结构化的变更数据
- 微服务数据同步: 各服务订阅相关表的变更数据