Flink CDC系列之:Kafka CSV 序列化器CsvSerializationSchema

这是一个 CSV 序列化器,负责将 Flink CDC 事件转换为 CSV 格式的数据。

类概述

java 复制代码
public class CsvSerializationSchema implements SerializationSchema<Event>

这个类实现了 Flink 的 SerializationSchema 接口,专门用于将 CDC 事件序列化为 CSV 格式的字节数组。

核心属性

java 复制代码
private static final long serialVersionUID = 1L;

/**
 * A map of {@link TableId} and its {@link SerializationSchema} to serialize Debezium JSON data.
 */
private final Map<TableId, TableSchemaInfo> csvSerializers;  // 表结构缓存

private final ZoneId zoneId;  // 时区配置

private InitializationContext context;  // Flink 初始化上下文

构造函数

java 复制代码
public CsvSerializationSchema(ZoneId zoneId) {
    this.zoneId = zoneId;
    csvSerializers = new HashMap<>();
}

参数说明:

  • zoneId:处理时区敏感数据(如时间戳)

核心方法详解

open() - 初始化方法

java 复制代码
@Override
public void open(InitializationContext context) {
    this.context = context;
}

作用:Flink 在启动序列化器时调用,用于初始化资源。

serialize() - 主序列化方法

java 复制代码
@Override
public byte[] serialize(Event event) {
    if (event instanceof SchemaChangeEvent) {
        handleSchemaChangeEvent((SchemaChangeEvent) event);  // 处理Schema变更
        return null;
    }
    DataChangeEvent dataChangeEvent = (DataChangeEvent) event;
    return serializeDataChangeEvent(dataChangeEvent);        // 处理数据变更
}

Schema 变更事件处理

java 复制代码
private void handleSchemaChangeEvent(SchemaChangeEvent schemaChangeEvent) {
    Schema schema;
    if (event instanceof CreateTableEvent) {
        // 新建表:获取表结构
        CreateTableEvent createTableEvent = (CreateTableEvent) event;
        schema = createTableEvent.getSchema();
    } else {
        // 更新表结构:应用Schema变更
        schema = SchemaUtils.applySchemaChangeEvent(
                csvSerializers.get(schemaChangeEvent.tableId()).getSchema(),
                schemaChangeEvent);
    }
    
    // 构建CSV序列化器
    CsvRowDataSerializationSchema csvSerializer = buildSerializationForPrimaryKey(schema);
    try {
        csvSerializer.open(context);  // 初始化序列化器
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    
    // 缓存表结构信息
    csvSerializers.put(
            schemaChangeEvent.tableId(),
            new TableSchemaInfo(
                    schemaChangeEvent.tableId(), schema, csvSerializer, zoneId));
}

数据变更事件处理

java 复制代码
private byte[] serializeDataChangeEvent(DataChangeEvent dataChangeEvent) {
    // 确定要序列化的数据
    RecordData recordData =
            dataChangeEvent.op().equals(OperationType.DELETE)
                    ? dataChangeEvent.before()  // 删除操作使用before数据
                    : dataChangeEvent.after();  // 其他操作使用after数据
    
    // 获取表结构信息
    TableSchemaInfo tableSchemaInfo = csvSerializers.get(dataChangeEvent.tableId());
    
    // 转换为RowData并序列化为CSV
    return tableSchemaInfo
            .getSerializationSchema()
            .serialize(tableSchemaInfo.getRowDataFromRecordData(recordData, true));
}

buildSerializationForPrimaryKey() - 构建CSV序列化器

java 复制代码
private CsvRowDataSerializationSchema buildSerializationForPrimaryKey(Schema schema) {
    // 构建包含表名和主键的字段数组
    DataField[] fields = new DataField[schema.primaryKeys().size() + 1];
    fields[0] = DataTypes.FIELD("TableId", DataTypes.STRING());  // 表标识字段
    
    // 添加所有主键字段
    for (int i = 0; i < schema.primaryKeys().size(); i++) {
        Column column = schema.getColumn(schema.primaryKeys().get(i)).get();
        fields[i + 1] = DataTypes.FIELD(column.getName(), column.getType());
    }
    
    // 构建数据类型
    DataType dataType = DataTypes.ROW(fields).notNull();
    LogicalType rowType = DataTypeUtils.toFlinkDataType(dataType).getLogicalType();
    
    // 创建CSV序列化器
    return new CsvRowDataSerializationSchema.Builder((RowType) rowType).build();
}

生成的 CSV 格式

数据结构

CSV 输出包含以下字段:

  • TableId:表标识(第一个字段)
  • 主键字段:所有主键列的值

示例输出

源表结构

java 复制代码
CREATE TABLE users (
    user_id BIGINT PRIMARY KEY,
    username VARCHAR(50),
    email VARCHAR(100),
    created_at TIMESTAMP
);

CREATE TABLE orders (
    order_id BIGINT PRIMARY KEY, 
    user_id BIGINT,
    amount DECIMAL(10,2),
    order_date DATE
);

CSV 输出示例

java 复制代码
# users 表数据
"users","1001"
"users","1002"

# orders 表数据  
"orders","5001"
"orders","5002"

完整数据 CSV 序列化

java 复制代码
// 可以修改为序列化所有字段
private CsvRowDataSerializationSchema buildSerializationForAllColumns(Schema schema) {
    DataField[] fields = new DataField[schema.getColumns().size() + 1];
    fields[0] = DataTypes.FIELD("TableId", DataTypes.STRING());
    for (int i = 0; i < schema.getColumns().size(); i++) {
        Column column = schema.getColumns().get(i);
        fields[i + 1] = DataTypes.FIELD(column.getName(), column.getType());
    }
    // ... 其余代码类似
}

这段代码定义了一个私有方法 buildSerializationForAllColumns,用于构建一个 CSV 行数据序列化模式。以下是逐行详细解释:

java 复制代码
private CsvRowDataSerializationSchema buildSerializationForAllColumns(Schema schema) {

声明一个私有的方法,返回类型为 CsvRowDataSerializationSchema,接受一个 Schema 对象作为参数。

java 复制代码
DataField[] fields = new DataField[schema.getColumns().size() + 1];

创建一个 DataField 数组,其长度比传入的 schema 中的列数多 1。这是因为除了原表的所有列外,还额外添加了一列(例如标识符或元数据)。

java 复制代码
fields[0] = DataTypes.FIELD("TableId", DataTypes.STRING());

将新数组的第一个元素设置为名为 "TableId" 的字段,数据类型为字符串。这通常用作表的唯一标识或其他元信息。

java 复制代码
for (int i = 0; i < schema.getColumns().size(); i++) {
    Column column = schema.getColumns().get(i);
    fields[i + 1] = DataTypes.FIELD(column.getName(), column.getType());
}

通过循环遍历 schema 中的所有列,从索引 1 开始填充 fields 数组。每个元素的名称和类型取自原始列的对应属性。

总结:这个 CsvSerializationSchema 是一个轻量级的序列化器,专门用于将 CDC 事件转换为包含表名和主键的 CSV 格式。它适用于需要轻量级变更通知、审计跟踪或主键级别数据同步的场景,通过只序列化关键信息来减少数据传输

相关推荐
阳光不锈@1 小时前
三大主流消息队列架构设计思想剖析:从 RabbitMQ 到 RocketMQ 的改造实践
kafka·rabbitmq·rocketmq·rabbit改造rocket·去除rabbitmq·rabbitmq改造
ZealSinger5 小时前
Boot4升后Kafka retry属性怎么改
kafka·retry·boot4·升级踩坑
starzy19906 小时前
KafkaProducer 源码深度剖析:处理粘包问题的完美方案
大数据·kafka
MrWind灬9 小时前
我给自己写了个 Kafka 桌面客户端:MrWindKafkaClient
kafka·pyqt
starzy19901 天前
KafkaProducer 源码深度剖析:与 Broker 建立连接
大数据·kafka
我不会起名字3221 天前
Kafka 重复消费:自动提交位点与手动提交的 3 个错位
kafka·消息队列·幂等·排查
ly76893 天前
Kafka 消费者重试与死信:Spring for Apache Kafka 的边界设计
spring·kafka·apache·并发消费·消费者重试·死信主题·位移提交
我不会起名字3223 天前
Kafka 顺序消息为什么乱了:分区数变化与重试的 3 类原因
kafka·消息队列·顺序·排查
每天一道题4 天前
Kafka 是如何工作的?从 Broker、Partition、Offset 到 Consumer Group
分布式·kafka
ClouGence4 天前
还在用命令行管理 Kafka?开源 CloudDM 让操作更简单
数据库·kafka·开源