Flink CDC系列之:Kafka JSON 序列化器JsonSerializationSchema

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

类概述

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

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

核心属性

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> jsonSerializers;  // 表结构缓存

// JSON 格式配置参数
private final TimestampFormat timestampFormat;           // 时间戳格式
private final JsonFormatOptions.MapNullKeyMode mapNullKeyMode;  // Map中null键处理模式
private final String mapNullKeyLiteral;                  // null键替换文本
private final boolean encodeDecimalAsPlainNumber;        // Decimal是否编码为普通数字
private final boolean ignoreNullFields;                  // 是否忽略null字段
private final ZoneId zoneId;                             // 时区配置

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

构造函数

java 复制代码
public JsonSerializationSchema(
        TimestampFormat timestampFormat,
        JsonFormatOptions.MapNullKeyMode mapNullKeyMode,
        String mapNullKeyLiteral,
        ZoneId zoneId,
        boolean encodeDecimalAsPlainNumber,
        boolean ignoreNullFields) {
    this.timestampFormat = timestampFormat;
    this.mapNullKeyMode = mapNullKeyMode;
    this.mapNullKeyLiteral = mapNullKeyLiteral;
    this.encodeDecimalAsPlainNumber = encodeDecimalAsPlainNumber;
    this.zoneId = zoneId;
    jsonSerializers = new HashMap<>();
    this.ignoreNullFields = ignoreNullFields;
}


核心方法详解
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(
                jsonSerializers.get(schemaChangeEvent.tableId()).getSchema(),
                schemaChangeEvent);
    }
    
    // 构建JSON序列化器
    JsonRowDataSerializationSchema jsonSerializer = buildSerializationForPrimaryKey(schema);
    try {
        jsonSerializer.open(context);  // 初始化序列化器
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    
    // 缓存表结构信息
    jsonSerializers.put(
            schemaChangeEvent.tableId(),
            new TableSchemaInfo(
                    schemaChangeEvent.tableId(), schema, jsonSerializer, zoneId));
}

buildSerializationForPrimaryKey() - 构建JSON序列化器

java 复制代码
private JsonRowDataSerializationSchema 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();
    
    // 使用兼容性工具类创建JSON序列化器
    return JsonRowDataSerializationSchemaUtils.createSerializationSchema(
            (RowType) rowType,
            timestampFormat,
            mapNullKeyMode,
            mapNullKeyLiteral,
            encodeDecimalAsPlainNumber,
            ignoreNullFields);
}

生成的 JSON 格式

数据结构

JSON 输出包含以下字段:

  • 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
);

JSON 输出示例

java 复制代码
// users 表数据
{
  "TableId": "users",
  "user_id": 1001
}

{
  "TableId": "users", 
  "user_id": 1002
}

// orders 表数据
{
  "TableId": "orders",
  "order_id": 5001
}

{
  "TableId": "orders",
  "order_id": 5002
}

JSON 格式配置详解

时间戳格式 (TimestampFormat)

java 复制代码
TimestampFormat.SQL         // "2023-11-24 15:30:00"
TimestampFormat.ISO_8601    // "2023-11-24T15:30:00Z"

Map中null键处理 (MapNullKeyMode)

java 复制代码
JsonFormatOptions.MapNullKeyMode.DROP       // 丢弃null键
JsonFormatOptions.MapNullKeyMode.FAIL       // 抛出异常
JsonFormatOptions.MapNullKeyMode.LITERAL    // 使用指定文本替换

Decimal编码方式

java 复制代码
encodeDecimalAsPlainNumber = true   // 123.45 编码为 123.45
encodeDecimalAsPlainNumber = false  // 123.45 编码为 "123.45"

Null字段处理

java 复制代码
ignoreNullFields = true    // 忽略null字段,不输出到JSON
ignoreNullFields = false   // 包含null字段,输出为 null

扩展为完整数据序列化

如果要序列化完整数据,可以修改 buildSerializationForPrimaryKey 方法:

java 复制代码
private JsonRowDataSerializationSchema 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());
    }
    
    DataType dataType = DataTypes.ROW(fields).notNull();
    LogicalType rowType = DataTypeUtils.toFlinkDataType(dataType).getLogicalType();
    
    return JsonRowDataSerializationSchemaUtils.createSerializationSchema(
            (RowType) rowType,
            timestampFormat,
            mapNullKeyMode,
            mapNullKeyLiteral,
            encodeDecimalAsPlainNumber,
            ignoreNullFields);
}

配置示例
Flink CDC Pipeline 配置

java 复制代码
mysql-sync-database \
--database source_db \
--sink-conf connector=kafka \
--sink-conf format=json \
--sink-conf json.timestamp-format=ISO-8601 \
--sink-conf json.map-null-key.mode=LITERAL \
--sink-conf json.map-null-key.literal=NULL \
--sink-conf json.encode-decimal-as-plain-number=true \
--sink-conf json.ignore-null-fields=true

Flink SQL 配置

java 复制代码
CREATE TABLE kafka_sink (
    -- 字段定义
) WITH (
    'connector' = 'kafka',
    'format' = 'json',
    'json.timestamp-format' = 'SQL', 
    'json.map-null-key.mode' = 'LITERAL',
    'json.map-null-key.literal' = 'null',
    'json.encode-decimal-as-plain-number' = 'false',
    'json.ignore-null-fields' = 'true'
);

总结:这个 JsonSerializationSchema 是一个专门用于将 CDC 事件序列化为 JSON 格式的组件,它通过维护表结构缓存和动态处理 Schema 变更,实现了高效的数据序列化。虽然当前实现只序列化表名和主键信息,但其设计支持轻松扩展为完整数据序列化。

相关推荐
东东2333 小时前
GeoJSON 介绍:Web 地图数据的通用语言
前端·javascript·json
songgz8 小时前
双向流式 JSON 解析架构:并行优化大型文件处理
java·开发语言·json
星尘库9 小时前
.NET Framework中报错命名空间System.Text中不存在类型或命名空间名Json
java·json·.net
Ka1Yan10 小时前
快速上手MySQL中的JSON函数语法——5.x+ / 8.x+
数据库·sql·mysql·json
风华浪浪13 小时前
python 基础之 jsonpatch 用于对 JSON 文档的局部更新操作
linux·python·json
n***26561 天前
MySQL JSON数据类型全解析(JSON datatype and functions)
android·mysql·json
阿巴~阿巴~3 天前
自定义协议设计与实践:从协议必要性到JSON流式处理
服务器·网络·网络协议·json·操作系统·自定义协议
最笨的羊羊3 天前
Flink CDC系列之:JSON 序列化器JsonRowDataSerializationSchemaUtils
json·flink cdc系列·serialization·json 序列化器·rowdata·schemautils
最笨的羊羊3 天前
Flink CDC系列之:Kafka CSV 序列化器CsvSerializationSchema
kafka·csv·schema·flink cdc系列·serialization·序列化器