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 变更,实现了高效的数据序列化。虽然当前实现只序列化表名和主键信息,但其设计支持轻松扩展为完整数据序列化。

相关推荐
胖头鱼的鱼缸(尹海文)1 天前
胖头鱼的技术专栏-474 从能跑到能上线:PG SplitJSON 0.2.0 和 0.1.0 差在哪(20261008)
postgresql·json
?? Daisy1 天前
Claude Code 接入 GPT:settings.json 两种配置方法(2026-10)
gpt·json
小静AI工程实验室1 天前
Python 爬虫解析 JSON-LD:多块 script、@graph 与坏数据的 9 个边界
爬虫·python·json
网络毒刘2 天前
为 MCP 写集成测试:mock stdio、断言 tool schema,防止升级后静默坏掉
测试·schema·mcp·atomgit
goehou2 天前
LLM 结构化输出全解:从 Prompt 约束到 Schema 硬保证,三层实现怎么选
ai·llm·json·agent·教程·结构化输出
独立开发者阿乐2 天前
结构化数据 JSON-LD 实战:让 AI 读懂你的网页
人工智能·json·结构化数据·geo优化·json-ld·ai收录·faqpage
liangshanbo12154 天前
高级前端面试题:package.json 常见字段
前端·json
梦帮科技4 天前
【3.0修订版】创世与节点工程:genesis.json 与 node.toml 解剖
数据结构·算法·json·去中心化·区块链·智能合约·信任链
weixin_440730505 天前
内置函数、json文本、pickle二进制
开发语言·python·json
念越5 天前
接口自动化测试从入门到实战:接口用例设计、Requests、Pytest、YAML、JSON Schema 与 Allure 报告
python·测试工具·自动化·json·pytest