Flink CDC系列之:flink-cdc-base模块dialect

DataSourceDialect

这是一个 数据源方言接口,定义了与不同类型数据库交互的统一抽象层。

接口概述

java 复制代码
/**
 * The dialect of data source.
 *
 * @param <C> The source config of data source.
 */
@Experimental
public interface DataSourceDialect<C extends SourceConfig> extends Serializable, Closeable {

方言模式: 为不同数据库提供特定实现

泛型参数: 支持不同类型的源配置

实验性: @Experimental 表示接口可能还在演进中

可序列化: 支持在分布式环境中传输

可关闭: 支持资源清理

核心方法详解
方言标识

java 复制代码
/** Get the name of dialect. */
String getName();

作用: 返回方言名称,如 "MySQL"、"PostgreSQL"、"Oracle" 等

数据发现

java 复制代码
/** Discovers the list of data collection to capture. */
List<TableId> discoverDataCollections(C sourceConfig);

作用: 发现需要捕获的数据表集合

返回: 表标识符列表

java 复制代码
/**
 * Discovers the captured data collections' schema by {@link SourceConfig}.
 */
Map<TableId, TableChanges.TableChange> discoverDataCollectionSchemas(C sourceConfig);

作用: 发现表结构定义

返回: 表ID到表结构变更的映射

偏移量管理

java 复制代码
/**
 * Displays current offset from the database e.g. query Mysql binary logs by query <code>
 * SHOW MASTER STATUS</code>.
 */
Offset displayCurrentOffset(C sourceConfig);

作用: 获取数据库当前偏移量(如 MySQL 的 binlog 位置)

java 复制代码
/** Displays committed offset from the database e.g. query Postgresql confirmed_lsn */
default Offset displayCommittedOffset(C sourceConfig) {
    throw new UnsupportedOperationException();
}

作用: 获取已提交的偏移量(某些数据库需要)

默认实现: 抛出异常,需要支持的数据库重写

标识符处理

java 复制代码
/** Check if the CollectionId is case-sensitive or not. */
boolean isDataCollectionIdCaseSensitive(C sourceConfig);

作用: 检查表名等标识符是否大小写敏感

重要性: 影响 SQL 查询的构建

数据分片

java 复制代码
/** Returns the {@link ChunkSplitter} which used to split collection to splits. */
@Deprecated
ChunkSplitter createChunkSplitter(C sourceConfig);

ChunkSplitter createChunkSplitter(C sourceConfig, ChunkSplitterState chunkSplitterState);

作用: 创建数据分片器,用于将大表拆分为并行处理的小块

演进: 新版本支持从状态恢复分片器

数据获取任务

java 复制代码
/** The fetch task used to fetch data of a snapshot split or stream split. */
FetchTask<SourceSplitBase> createFetchTask(SourceSplitBase sourceSplitBase);

作用: 创建数据获取任务,用于读取快照分片或流分片

java 复制代码
/** The task context used for fetch task to fetch data from external systems. */
FetchTask.Context createFetchTaskContext(C sourceConfig);

作用: 创建获取任务的上下文(如数据库连接等)

检查点通知

java 复制代码
/**
 * We may need the offset corresponding to the checkpointId. For example, we should commit LSN
 * of checkpoint to postgres's slot.
 */
default void notifyCheckpointComplete(long checkpointId, Offset offset) throws Exception {}

作用: 检查点完成时通知方言(如提交 PostgreSQL 的 LSN)

应用: 确保不会重复读取已处理的数据

表过滤检查

java 复制代码
/** Check if the tableId is included in SourceConfig. */
boolean isIncludeDataCollection(C sourceConfig, TableId tableId);

作用: 检查表是否在配置的捕获范围内

具体实现示例
MySQL 方言实现

java 复制代码
public class MySqlDataSourceDialect implements DataSourceDialect<MySqlSourceConfig> {
    
    @Override
    public String getName() {
        return "MySQL";
    }
    
    @Override
    public List<TableId> discoverDataCollections(MySqlSourceConfig sourceConfig) {
        // 使用 JDBC 查询 information_schema 获取表列表
        try (Connection conn = getConnection(sourceConfig)) {
            DatabaseMetaData metaData = conn.getMetaData();
            ResultSet tables = metaData.getTables(
                sourceConfig.getDatabaseList().get(0), null, null, new String[]{"TABLE"});
            List<TableId> tableIds = new ArrayList<>();
            while (tables.next()) {
                tableIds.add(new TableId(tables.getString("TABLE_CAT"), 
                                       tables.getString("TABLE_SCHEM"), 
                                       tables.getString("TABLE_NAME")));
            }
            return tableIds;
        }
    }
    
    @Override
    public Offset displayCurrentOffset(MySqlSourceConfig sourceConfig) {
        // 执行 SHOW MASTER STATUS 获取 binlog 位置
        try (Connection conn = getConnection(sourceConfig);
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SHOW MASTER STATUS")) {
            if (rs.next()) {
                String file = rs.getString("File");
                long position = rs.getLong("Position");
                return new BinlogOffset(file, position);
            }
        }
        return null;
    }
    
    @Override
    public ChunkSplitter createChunkSplitter(MySqlSourceConfig sourceConfig, 
                                           ChunkSplitterState chunkSplitterState) {
        return new MySqlChunkSplitter(sourceConfig, chunkSplitterState);
    }
    
    // ... 其他方法实现
}

PostgreSQL 方言实现

java 复制代码
public class PostgresDataSourceDialect implements DataSourceDialect<PostgresSourceConfig> {
    
    @Override
    public String getName() {
        return "PostgreSQL";
    }
    
    @Override
    public List<TableId> discoverDataCollections(PostgresSourceConfig sourceConfig) {
        // 查询 pg_catalog.pg_tables 获取表列表
        String sql = "SELECT schemaname, tablename FROM pg_catalog.pg_tables " +
                    "WHERE schemaname NOT IN ('pg_catalog', 'information_schema')";
        // ... 执行查询并返回 TableId 列表
    }
    
    @Override
    public Offset displayCommittedOffset(PostgresSourceConfig sourceConfig) {
        // 查询 pg_replication_slots 获取 confirmed_flush_lsn
        String sql = "SELECT confirmed_flush_lsn FROM pg_replication_slots WHERE slot_name = ?";
        // ... 执行查询并返回 LSN 偏移量
    }
    
    @Override
    public void notifyCheckpointComplete(long checkpointId, Offset offset) throws Exception {
        // 向 PostgreSQL 复制槽确认 LSN
        String sql = "SELECT pg_replication_origin_advance(?, ?)";
        // ... 执行确认
    }
    
    // ... 其他方法实现
}

设计模式分析
策略模式

java 复制代码
// 不同数据库使用不同的方言策略
DataSourceDialect<MySqlSourceConfig> mysqlDialect = new MySqlDataSourceDialect();
DataSourceDialect<PostgresSourceConfig> postgresDialect = new PostgresDataSourceDialect();

模板方法模式

接口定义算法骨架,具体实现提供特定数据库的逻辑。

工厂模式

方言通常由工厂创建:

java 复制代码
public class DataSourceDialectFactory {
    public static DataSourceDialect createDialect(SourceConfig sourceConfig) {
        if (sourceConfig instanceof MySqlSourceConfig) {
            return new MySqlDataSourceDialect();
        } else if (sourceConfig instanceof PostgresSourceConfig) {
            return new PostgresDataSourceDialect();
        }
        throw new UnsupportedOperationException("Unsupported database");
    }
}

在 Flink CDC 架构中的角色
数据流处理中的方言作用:

java 复制代码
Flink CDC Framework
    ↓
DataSourceDialect (统一接口)
    ↓
具体数据库方言 (MySQLDialect、PostgresDialect等)
    ↓  
数据库特定操作 (SHOW MASTER STATUS、pg_replication_slots等)

配置和使用示例
在源创建中使用方言

java 复制代码
public class MySqlIncrementalSource<C extends SourceConfig> implements IncrementalSource {
    
    private final DataSourceDialect<C> dialect;
    private final C sourceConfig;
    
    public MySqlIncrementalSource(C sourceConfig) {
        this.sourceConfig = sourceConfig;
        this.dialect = new MySqlDataSourceDialect();
    }
    
    @Override
    public List<TableId> discoverTables() {
        return dialect.discoverDataCollections(sourceConfig);
    }
    
    @Override
    public List<SourceSplitBase> createSplits() {
        ChunkSplitter splitter = dialect.createChunkSplitter(sourceConfig, null);
        return splitter.generateSplits();
    }
    
    // ... 其他方法
}

任务执行流程

java 复制代码
// 在数据读取任务中
public class MySqlFetchTask implements FetchTask<SourceSplitBase> {
    private final DataSourceDialect dialect;
    private final SourceSplitBase split;
    
    public void execute(FetchTask.Context context) {
        // 使用方言创建数据库特定的读取逻辑
        if (split.isSnapshotSplit()) {
            // 执行快照数据读取
            readSnapshotData(split, context);
        } else {
            // 执行增量数据读取  
            readStreamData(split, context);
        }
    }
}

扩展性设计
支持新数据库

要支持新的数据库,只需:

  • 实现 DataSourceDialect 接口
  • 提供数据库特定的配置类(继承 JdbcSourceConfig)
  • 实现所有抽象方法

默认实现策略

接口提供了合理的默认实现,降低新方言的实现难度:

java 复制代码
default void notifyCheckpointComplete(long checkpointId, Offset offset) throws Exception {}
default void close() throws IOException {}

总结

DataSourceDialect 接口是 Flink CDC 框架的核心抽象,它:

  • 提供统一抽象层: 屏蔽不同数据库的差异
  • 支持多数据库: 通过具体方言实现支持 MySQL、PostgreSQL、Oracle 等
  • 封装数据库特性: 每个方法对应特定的数据库操作
  • 支持状态管理: 分片器支持从状态恢复
  • 保证数据一致性: 通过偏移量管理和检查点通知
  • 易于扩展: 新数据库只需实现接口即可接入

这个接口使得 Flink CDC 能够以统一的方式处理各种关系型数据库,是构建通用 CDC 框架的关键设计。

JdbcDataSourceDialect

这是一个 JDBC 数据源方言接口,专门为基于 JDBC 的关系型数据库提供方言抽象。

接口概述

java 复制代码
/** The dialect of JDBC data source. */
@Experimental
public interface JdbcDataSourceDialect extends DataSourceDialect<JdbcSourceConfig> {

继承关系: 继承自 DataSourceDialect

专门化: 针对 JDBC 数据源的特化接口

配置类型: 使用 JdbcSourceConfig 作为配置类型

方法详解
数据发现方法(重写父接口)

java 复制代码
/** Discovers the list of table to capture. */
@Override
List<TableId> discoverDataCollections(JdbcSourceConfig sourceConfig);

/** Discovers the captured tables' schema by {@link SourceConfig}. */
@Override
Map<TableId, TableChange> discoverDataCollectionSchemas(JdbcSourceConfig sourceConfig);

特点:

重写父接口方法,将参数类型特化为 JdbcSourceConfig

提供 JDBC 特定的数据发现实现

JDBC 连接管理

java 复制代码
/**
 * Creates and opens a new {@link JdbcConnection} backing connection pool.
 *
 * @param sourceConfig a basic source configuration.
 * @return a utility that simplifies using a JDBC connection.
 */
JdbcConnection openJdbcConnection(JdbcSourceConfig sourceConfig);

作用: 创建并打开 JDBC 连接

返回: Debezium 的 JdbcConnection 工具类,简化 JDBC 操作

java 复制代码
/** Get a connection pool factory to create connection pool. */
JdbcConnectionPoolFactory getPooledDataSourceFactory();

作用: 获取连接池工厂,用于创建和管理数据库连接池

表结构查询

java 复制代码
/** Query and build the schema of table. */
TableChange queryTableSchema(JdbcConnection jdbc, TableId tableId);

作用: 查询并构建单个表的模式信息

参数:

  • jdbc: JDBC 连接
  • tableId: 表标识符
  • 返回: 表结构变更信息

任务创建方法(重写父接口)

java 复制代码
@Override
FetchTask<SourceSplitBase> createFetchTask(SourceSplitBase sourceSplitBase);

作用: 创建 JDBC 特定的数据获取任务

资源清理方法

java 复制代码
default void close() throws IOException {
    JdbcConnectionPools.getInstance(getPooledDataSourceFactory()).clear();
}

作用: 清理连接池资源

实现: 通过 JdbcConnectionPools 单例清理所有连接池

设计特点分析
JDBC 专门化

java 复制代码
// 父接口使用泛型 SourceConfig
public interface DataSourceDialect<C extends SourceConfig>

// 子接口特化为 JdbcSourceConfig  
public interface JdbcDataSourceDialect extends DataSourceDialect<JdbcSourceConfig>

优势:

类型安全,避免配置类型错误

可以直接访问 JDBC 特定配置

连接池集成

java 复制代码
JdbcConnection openJdbcConnection(JdbcSourceConfig sourceConfig);
JdbcConnectionPoolFactory getPooledDataSourceFactory();

设计意图:

  • 统一连接管理
  • 支持连接复用,提高性能
  • 避免连接泄漏

表结构查询分离

java 复制代码
TableChange queryTableSchema(JdbcConnection jdbc, TableId tableId);

作用: 提供细粒度的表结构查询,便于:

  • 增量发现新表
  • 处理表结构变更
  • 按需查询特定表

具体实现示例
MySQL JDBC 方言实现

java 复制代码
public class MySqlJdbcDataSourceDialect implements JdbcDataSourceDialect {
    
    private final JdbcConnectionPoolFactory connectionPoolFactory = 
        new MySqlConnectionPoolFactory();
    
    @Override
    public List<TableId> discoverDataCollections(JdbcSourceConfig sourceConfig) {
        try (JdbcConnection jdbc = openJdbcConnection(sourceConfig)) {
            // 使用 JDBC 查询 information_schema 获取表列表
            return jdbc.queryAndMap(
                "SELECT TABLE_SCHEMA, TABLE_NAME FROM information_schema.tables " +
                "WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA NOT IN ('mysql', 'sys', 'performance_schema')",
                rs -> {
                    List<TableId> tables = new ArrayList<>();
                    while (rs.next()) {
                        String schema = rs.getString(1);
                        String table = rs.getString(2);
                        tables.add(new TableId(sourceConfig.getDatabaseList().get(0), schema, table));
                    }
                    return tables;
                });
        }
    }
    
    @Override
    public JdbcConnection openJdbcConnection(JdbcSourceConfig sourceConfig) {
        // 创建 MySQL JDBC 连接
        Properties props = new Properties();
        props.setProperty("user", sourceConfig.getUsername());
        props.setProperty("password", sourceConfig.getPassword());
        props.setProperty("connectTimeout", String.valueOf(sourceConfig.getConnectTimeout().toMillis()));
        
        String url = String.format("jdbc:mysql://%s:%d/%s", 
            sourceConfig.getHostname(), 
            sourceConfig.getPort(), 
            sourceConfig.getDatabaseList().get(0));
            
        return new JdbcConnection(url, props, getPooledDataSourceFactory());
    }
    
    @Override
    public JdbcConnectionPoolFactory getPooledDataSourceFactory() {
        return connectionPoolFactory;
    }
    
    @Override
    public TableChange queryTableSchema(JdbcConnection jdbc, TableId tableId) {
        // 查询特定表的详细结构
        return jdbc.getTableSchema(tableId);
    }
    
    @Override
    public FetchTask<SourceSplitBase> createFetchTask(SourceSplitBase sourceSplitBase) {
        if (sourceSplitBase.isSnapshotSplit()) {
            return new MySqlSnapshotFetchTask(sourceSplitBase);
        } else {
            return new MySqlStreamFetchTask(sourceSplitBase);
        }
    }
    
    @Override
    public String getName() {
        return "MySQL-JDBC";
    }
    
    // ... 其他方法实现
}

PostgreSQL JDBC 方言实现

java 复制代码
public class PostgresJdbcDataSourceDialect implements JdbcDataSourceDialect {
    
    private final JdbcConnectionPoolFactory connectionPoolFactory = 
        new PostgresConnectionPoolFactory();
    
    @Override
    public List<TableId> discoverDataCollections(JdbcSourceConfig sourceConfig) {
        try (JdbcConnection jdbc = openJdbcConnection(sourceConfig)) {
            // 查询 pg_catalog 获取表列表
            return jdbc.queryAndMap(
                "SELECT schemaname, tablename FROM pg_catalog.pg_tables " +
                "WHERE schemaname NOT IN ('pg_catalog', 'information_schema')",
                rs -> {
                    List<TableId> tables = new ArrayList<>();
                    while (rs.next()) {
                        String schema = rs.getString(1);
                        String table = rs.getString(2);
                        tables.add(new TableId(sourceConfig.getDatabaseList().get(0), schema, table));
                    }
                    return tables;
                });
        }
    }
    
    @Override
    public JdbcConnection openJdbcConnection(JdbcSourceConfig sourceConfig) {
        Properties props = new Properties();
        props.setProperty("user", sourceConfig.getUsername());
        props.setProperty("password", sourceConfig.getPassword());
        props.setProperty("connectTimeout", String.valueOf(sourceConfig.getConnectTimeout().toSeconds()));
        
        String url = String.format("jdbc:postgresql://%s:%d/%s", 
            sourceConfig.getHostname(), 
            sourceConfig.getPort(), 
            sourceConfig.getDatabaseList().get(0));
            
        return new JdbcConnection(url, props, getPooledDataSourceFactory());
    }
    
    @Override
    public TableChange queryTableSchema(JdbcConnection jdbc, TableId tableId) {
        // PostgreSQL 特定的表结构查询
        return jdbc.readTableSchema(tableId.catalog(), tableId.schema(), tableId.table());
    }
    
    // ... 其他方法实现
}

连接池管理架构
连接池工厂设计

java 复制代码
public interface JdbcConnectionPoolFactory {
    DataSource createDataSource(JdbcSourceConfig sourceConfig);
    void closeDataSource(DataSource dataSource);
}

// MySQL 连接池工厂实现
public class MySqlConnectionPoolFactory implements JdbcConnectionPoolFactory {
    @Override
    public DataSource createDataSource(JdbcSourceConfig sourceConfig) {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(String.format("jdbc:mysql://%s:%d/%s", 
            sourceConfig.getHostname(), sourceConfig.getPort(), 
            sourceConfig.getDatabaseList().get(0)));
        config.setUsername(sourceConfig.getUsername());
        config.setPassword(sourceConfig.getPassword());
        config.setMaximumPoolSize(sourceConfig.getConnectionPoolSize());
        config.setConnectionTimeout(sourceConfig.getConnectTimeout().toMillis());
        return new HikariDataSource(config);
    }
}

连接池管理器

java 复制代码
public class JdbcConnectionPools {
    private static JdbcConnectionPools instance;
    private final Map<String, DataSource> dataSourceMap = new ConcurrentHashMap<>();
    private final JdbcConnectionPoolFactory factory;
    
    public static JdbcConnectionPools getInstance(JdbcConnectionPoolFactory factory) {
        if (instance == null) {
            synchronized (JdbcConnectionPools.class) {
                if (instance == null) {
                    instance = new JdbcConnectionPools(factory);
                }
            }
        }
        return instance;
    }
    
    public DataSource getDataSource(JdbcSourceConfig config) {
        String key = generateKey(config);
        return dataSourceMap.computeIfAbsent(key, k -> factory.createDataSource(config));
    }
    
    public void clear() {
        dataSourceMap.values().forEach(factory::closeDataSource);
        dataSourceMap.clear();
    }
}

在 Flink CDC 架构中的位置
方言层次结构:

java 复制代码
DataSourceDialect<SourceConfig> (通用数据源方言)
    ↑
JdbcDataSourceDialect (JDBC数据源方言)  
    ↑
具体JDBC方言 (MySqlJdbcDataSourceDialect、PostgresJdbcDataSourceDialect等)

数据读取流程中的角色:

java 复制代码
IncrementalSource
    → JdbcDataSourceDialect.discoverDataCollections()  // 发现表
    → JdbcDataSourceDialect.openJdbcConnection()      // 打开连接
    → JdbcDataSourceDialect.createFetchTask()         // 创建读取任务
    → FetchTask.execute()                            // 执行数据读取

配置和使用示例
创建和使用方言

java 复制代码
public class MySqlIncrementalSource extends IncrementalSource<MySqlSourceConfig> {
    
    private final JdbcDataSourceDialect dialect;
    
    public MySqlIncrementalSource(MySqlSourceConfig sourceConfig) {
        super(sourceConfig);
        this.dialect = new MySqlJdbcDataSourceDialect();
    }
    
    @Override
    protected void discoverTables() {
        // 使用方言发现表
        List<TableId> tables = dialect.discoverDataCollections(sourceConfig);
        // 处理发现的表...
    }
    
    @Override
    protected void createSplits() {
        // 使用方言创建分片器
        ChunkSplitter splitter = dialect.createChunkSplitter(sourceConfig, null);
        List<SourceSplitBase> splits = splitter.generateSplits();
        // 分配分片...
    }
    
    @Override
    public void close() throws Exception {
        // 清理资源
        dialect.close();
        super.close();
    }
}

表结构发现流程

java 复制代码
public Map<TableId, TableChange> discoverTableSchemas(JdbcSourceConfig config) {
    JdbcDataSourceDialect dialect = getDialect(config);
    Map<TableId, TableChange> schemas = new HashMap<>();
    
    try (JdbcConnection jdbc = dialect.openJdbcConnection(config)) {
        List<TableId> tables = dialect.discoverDataCollections(config);
        
        for (TableId tableId : tables) {
            // 逐个查询表结构
            TableChange schema = dialect.queryTableSchema(jdbc, tableId);
            schemas.put(tableId, schema);
        }
    }
    
    return schemas;
}

扩展性设计
支持新的 JDBC 数据库

要支持新的 JDBC 数据库,需要:

  • 实现 JdbcDataSourceDialect 接口
  • 提供数据库特定的连接池工厂
  • 实现表发现和结构查询逻辑
  • 创建对应的获取任务实现

连接管理优化

接口设计支持:

  • 连接池复用
  • 连接参数优化
  • 连接健康检查
  • 自动重连机制

总结

JdbcDataSourceDialect 接口:

  • JDBC 专门化: 为基于 JDBC 的关系型数据库提供统一抽象
  • 连接池集成: 内置连接池管理,提高性能和资源利用率
  • 细粒度控制: 提供单个表结构查询等细粒度操作
  • 资源管理: 自动清理连接池等资源
  • 类型安全: 特化的配置类型避免运行时错误
  • 易于实现: 基于 JDBC 标准,新数据库易于支持

这个接口是 Flink CDC JDBC 连接器的核心,为各种关系型数据库提供了统一而高效的接入方式。

相关推荐
最笨的羊羊1 天前
Flink CDC系列之:JSON 序列化器JsonRowDataSerializationSchemaUtils
json·flink cdc系列·serialization·json 序列化器·rowdata·schemautils
最笨的羊羊1 天前
Flink CDC系列之:Kafka CSV 序列化器CsvSerializationSchema
kafka·csv·schema·flink cdc系列·serialization·序列化器
最笨的羊羊1 天前
Flink CDC系列之:Kafka的Debezium JSON 结构定义类DebeziumJsonStruct
kafka·debezium·flink cdc系列·debezium json·结构定义类·jsonstruct
最笨的羊羊1 天前
Flink CDC系列之:Kafka JSON 序列化类型枚举
flink cdc系列·kafka json·序列化类型枚举
最笨的羊羊2 天前
Flink CDC系列之:数据接收器工厂类DorisDataSinkFactory
doris·flink cdc系列·数据接收器工厂类·datasinkfactory
最笨的羊羊8 天前
Flink CDC系列之:Apache Flink CDC 项目的各个模块作用
flink cdc系列·各个模块作用
最笨的羊羊1 年前
Flink CDC系列之:学习理解核心概念——Data Sink
flink cdc系列·学习理解核心概念·data sink
最笨的羊羊1 年前
Flink CDC系列之:学习理解核心概念——Data Source
flink cdc系列·data source·学习理解核心概念
最笨的羊羊1 年前
Flink CDC系列之:学习理解核心概念——Data Pipeline
flink cdc系列·学习理解核心概念·data pipeline