DB2-Db2SnapshotChangeEventSource

提示:Db2SnapshotChangeEventSource 类的主要作用是在Debezium框架内为DB2数据库提供一种机制,用于捕获数据库的当前状态,即快照,并将这些状态变化转换为一系列的变更事件,从而实现数据的实时复制和同步。这对于需要实时数据同步的场景非常关键,例如在微服务架构中保持数据一致性,或者在实时分析系统中提供最新的数据视图。


前言

提示:这个类主要作用是在 Debezium 连接器中为 DB2 数据库提供快照功能,确保能够捕获数据库的完整状态,并将这些状态转换为变更事件流,供下游系统消费


提示:以下是本篇文章正文内容

一、核心功能

核心功能详细说明

1. 初始化与配置

  • 构造函数初始化:接收多个参数,包括配置、连接工厂、数据库模式、事件调度器等,用于初始化 Db2SnapshotChangeEventSource 实例。
  • 内部连接管理:通过 MainConnectionProvidingConnectionFactory 创建与 DB2 数据库的实际连接,用于执行快照操作。

2. 快照上下文准备

  • 快照上下文创建:通过 prepare 方法为每个分区准备快照上下文,包括数据库名称和是否按需启动快照的信息。
  • 事务隔离级别记录:在快照开始前,记录数据库连接当前的事务隔离级别,以便在快照结束后恢复原状。

3. 表结构与数据读取

  • 获取所有表ID:通过 getAllTableIds 方法读取数据库中所有表的 TableId,用于快照操作。
  • 读取表结构:在 readTableStructure 方法中,读取数据库中所有相关表的结构信息,包括表的定义和约束,这是构建快照的基础。

4. 事务隔离与表锁定

  • 事务隔离控制:根据配置的快照隔离模式 (SnapshotIsolationMode),在快照过程中调整数据库连接的事务隔离级别,以保证快照的一致性和准确性。
  • 表锁定:在 lockTablesForSchemaSnapshot 方法中,根据隔离模式锁定数据库中的表,防止快照过程中数据发生变化,确保快照的一致性。

5. 偏移量管理

  • 偏移量确定:在 determineSnapshotOffset 方法中,根据配置的快照模式和上一次快照的偏移量,确定本次快照的起始位置,用于跟踪数据库状态的变化。

6. 快照完成与清理

  • 快照完成处理:在 completed 方法中,处理快照完成后的资源释放和状态清理。
  • 快照中止处理:在 aborted 方法中,处理快照中止时的资源释放和状态清理。

7. SQL查询生成与事件转换

  • SQL查询生成:在 getSnapshotSelect 方法中,生成用于快照的 SQL 查询语句,用于读取表中的数据。
  • 事件生成与发送:将快照过程中读取到的数据转换为变更事件,通过事件调度器发送至下游系统,实现数据的实时复制和同步。

8. 状态恢复

  • 事务隔离级别恢复:在快照结束后,通过 close 方法恢复数据库连接的事务隔离级别至快照前的状态。

二、代码分析

// Db2SnapshotChangeEventSource 类的定义,它是 Debezium 中用于 DB2 数据库的快照变更事件源。
public class Db2SnapshotChangeEventSource extends RelationalSnapshotChangeEventSource<Db2Partition, Db2OffsetContext> {

    // 构造函数,初始化必要的配置和连接。
    public Db2SnapshotChangeEventSource(Db2ConnectorConfig connectorConfig, MainConnectionProvidingConnectionFactory<Db2Connection> connectionFactory,
                                        Db2DatabaseSchema schema, EventDispatcher<Db2Partition, TableId> dispatcher, Clock clock,
                                        SnapshotProgressListener<Db2Partition> snapshotProgressListener,
                                        NotificationService<Db2Partition, Db2OffsetContext> notificationService, SnapshotterService snapshotterService) {
        // 调用父类构造函数,初始化基本的快照变更事件源。
        super(connectorConfig, connectionFactory, schema, dispatcher, clock, snapshotProgressListener, notificationService, snapshotterService);
        // 保存配置和数据库连接。
        this.connectorConfig = connectorConfig;
        this.jdbcConnection = connectionFactory.mainConnection();
    }

    // 准备快照上下文。
    @Override
    protected SnapshotContext<Db2Partition, Db2OffsetContext> prepare(Db2Partition partition, boolean onDemand) {
        // 创建并返回一个新的 Db2SnapshotContext 实例,包含分区信息、数据库名称和是否按需启动快照的信息。
        return new Db2SnapshotContext(partition, jdbcConnection.getRealDatabaseName(), onDemand);
    }

    // 锁定表以进行快照。
    @Override
    protected void lockTablesForSchemaSnapshot(ChangeEventSourceContext sourceContext, RelationalSnapshotContext<Db2Partition, Db2OffsetContext> snapshotContext)
            throws SQLException, InterruptedException {
        // 根据配置的快照隔离模式,设置数据库连接的事务隔离级别。
        if (connectorConfig.getSnapshotIsolationMode() == SnapshotIsolationMode.READ_UNCOMMITTED) {
            jdbcConnection.connection().setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
            LOGGER.info("Schema locking was disabled in connector configuration");
        } else if (connectorConfig.getSnapshotIsolationMode() == SnapshotIsolationMode.READ_COMMITTED) {
            jdbcConnection.connection().setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
            LOGGER.info("Schema locking was disabled in connector configuration");
        } else if (connectorConfig.getSnapshotIsolationMode() == SnapshotIsolationMode.EXCLUSIVE
                || connectorConfig.getSnapshotIsolationMode() == SnapshotIsolationMode.REPEATABLE_READ) {
            // 设置事务隔离级别为 REPEATABLE_READ。
            jdbcConnection.connection().setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
            // 创建保存点,用于在快照后回滚锁定。
            ((Db2SnapshotContext) snapshotContext).preSchemaSnapshotSavepoint = jdbcConnection.connection().setSavepoint("db2_schema_snapshot");
            LOGGER.info("Executing schema locking");
            try (Statement statement = jdbcConnection.connection().createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
                // 遍历所有要捕获的表,执行锁定语句。
                for (TableId tableId : snapshotContext.capturedTables) {
                    // 检查快照是否被中断。
                    if (!sourceContext.isRunning()) {
                        throw new InterruptedException("Interrupted while locking table " + tableId);
                    }
                    // 获取锁定语句,如果存在,则执行锁定。
                    Optional<String> lockingStatement = snapshotterService.getSnapshotLock().tableLockingStatement(connectorConfig.snapshotLockTimeout(),
                            quoteTableName(tableId));
                    if (lockingStatement.isPresent()) {
                        LOGGER.info("Locking table {}", tableId);
                        statement.executeQuery(lockingStatement.get()).close();
                    }
                }
            }
        } else {
            throw new IllegalStateException("Unknown locking mode specified.");
        }
    }

    // 确定快照的偏移量。
    @Override
    protected void determineSnapshotOffset(RelationalSnapshotContext<Db2Partition, Db2OffsetContext> ctx, Db2OffsetContext previousOffset) throws Exception {
        // 如果不是始终进行快照模式且有上一个偏移量,则直接使用上一个偏移量。
        if (connectorConfig.getSnapshotMode() != Db2ConnectorConfig.SnapshotMode.ALWAYS && previousOffset != null) {
            ctx.offset = previousOffset;
            tryStartingSnapshot(ctx);
            return;
        }
        // 否则,创建一个新的 Db2OffsetContext,基于数据库的最大 LSN。
        ctx.offset = new Db2OffsetContext(
                connectorConfig,
                TxLogPosition.valueOf(jdbcConnection.getMaxLsn()),
                false,
                false);
    }

    /**
 * 读取Db2数据库中的表结构,基于提供的上下文和任务需求。
 * 此方法专门用于查询需要捕获的表的结构,
 * 避免不必要的查询以提高效率。
 *
 * @param sourceContext 变更事件源的上下文,用于检查任务是否仍在运行。
 * @param snapshotContext 快照上下文,包含了分区信息和偏移量上下文。
 * @param previousOffset 上一次快照的偏移量上下文。
 * @param snapshottingTask 快照任务,决定快照行为。
 * @throws SQLException 如果数据库访问出现错误。
 * @throws InterruptedException 如果读取表结构时任务被中断。
 */

protected void readTableStructure(ChangeEventSourceContext sourceContext,
                                      RelationalSnapshotContext<Db2Partition, Db2OffsetContext> snapshotContext,
                                      Db2OffsetContext previousOffset, SnapshottingTask snapshottingTask)
            throws SQLException, InterruptedException {

    // 根据捕获的表流收集所有相关的模式名到集合中
    Set<String> schemas = snapshotContext.capturedTables.stream()
            .map(TableId::schema)
            .collect(Collectors.toSet());

    // 针对我们感兴趣的模式读取信息,根据捕获的表集;
    // 虽然传递的表名过滤器本身会跳过所有未包含的表,但这种方式读取模式会快得多
    for (String schema : schemas) {
        if (!sourceContext.isRunning()) {
            // 如果任务被中断,抛出异常
            throw new InterruptedException("在读取模式 " + schema + " 的结构时被中断");
        }

        // 输出正在读取的模式信息
        LOGGER.info("正在读取模式 '{}' 的结构", schema);

        // 根据快照任务是否按需执行设置表过滤器
        Tables.TableFilter tableFilter = snapshottingTask.isOnDemand() 
                ? Tables.TableFilter.fromPredicate(snapshotContext.capturedTables::contains)
                : connectorConfig.getTableFilters().dataCollectionFilter();

        // 使用JDBC连接读取模式结构
        jdbcConnection.readSchema(
                snapshotContext.tables,
                null,
                schema,
                tableFilter,
                null,
                false);
    }
}

    /**
     * 获取创建表的模式变更事件。
     * 
     * @param snapshotContext 快照上下文,包含分区信息、偏移量和目录名称。
     * @param table           表对象,表示数据库中的一个表。
     * @return SchemaChangeEvent 对象,代表创建表的事件。
     */
    protected SchemaChangeEvent getCreateTableEvent(RelationalSnapshotContext<Db2Partition, Db2OffsetContext> snapshotContext,
                                                    Table table) {
        return SchemaChangeEvent.ofSnapshotCreate(snapshotContext.partition, snapshotContext.offset, snapshotContext.catalogName, table);
    }

    /**
     * 当快照完成时调用的方法。
     * 
     * @param snapshotContext 快照上下文,用于关闭资源或执行清理操作。
     */
    @Override
    protected void completed(SnapshotContext<Db2Partition, Db2OffsetContext> snapshotContext) {
        close(snapshotContext);
    }

    /**
     * 当快照被中止时调用的方法。
     * 
     * @param snapshotContext 快照上下文,用于关闭资源或执行清理操作。
     */
    @Override
    protected void aborted(SnapshotContext<Db2Partition, Db2OffsetContext> snapshotContext) {
        close(snapshotContext);
    }

    /**
     * 关闭快照上下文中相关的资源。
     * 
     * @param snapshotContext 快照上下文,包含用于关闭的连接和隔离级别信息。
     */
    private void close(SnapshotContext<Db2Partition, Db2OffsetContext> snapshotContext) {
        try {
            jdbcConnection.connection().setTransactionIsolation(((Db2SnapshotContext) snapshotContext).isolationLevelBeforeStart);
        }
        catch (SQLException e) {
            throw new RuntimeException("设置事务隔离级别失败.", e);
        }
    }

    /**
     * 为指定的表生成有效的 DB2 查询字符串。
     * 
     * @param tableId   表标识符,用于生成查询。
     * @param columns   列名列表,用于选择操作。
     * @return 包含有效查询字符串的 Optional 对象。
     */
    @Override
    protected Optional<String> getSnapshotSelect(RelationalSnapshotContext<Db2Partition, Db2OffsetContext> snapshotContext, TableId tableId, List<String> columns) {

        return snapshotterService.getSnapshotQuery().snapshotQuery(quoteTableName(tableId), columns);
    }

    /**
     * 可变上下文类,在快照过程中填充具体信息。
     */
    private static class Db2SnapshotContext extends RelationalSnapshotContext<Db2Partition, Db2OffsetContext> {

        private int isolationLevelBeforeStart;
        private Savepoint preSchemaSnapshotSavepoint;

        Db2SnapshotContext(Db2Partition partition, String catalogName, boolean onDemand) {
            super(partition, catalogName, onDemand);
        }
    }

    /**
     * 复制偏移量上下文。
     * 
     * @param snapshotContext 快照上下文,从中获取原始偏移量。
     * @return 新的 Db2OffsetContext 对象,复制了原始偏移量的信息。
     */
    @Override
    protected Db2OffsetContext copyOffset(RelationalSnapshotContext<Db2Partition, Db2OffsetContext> snapshotContext) {
        return new Loader(connectorConfig).load(snapshotContext.offset.getOffset());
    }
}
类职责

Db2SnapshotChangeEventSource 是一个针对DB2数据库的具体实现,它主要封装了Debezium连接器在设置阶段捕获数据库初始状态所需的所有逻辑。这包括管理事务、锁定表、读取模式和数据以及处理偏移量。

继承与多态

该类继承自RelationalSnapshotChangeEventSource,后者为关系型数据库快照提供了一个通用框架。通过继承,Db2SnapshotChangeEventSource能够重写或扩展基类的方法,以适应DB2数据库特有的需求,如事务隔离级别、表锁定策略等。这种设计利用了面向对象编程中的继承和多态特性,使得代码更加灵活和可复用。

封装性

Db2SnapshotChangeEventSource通过其内部类Db2SnapshotContext来封装快照上下文,包括事务隔离级别、保存点等信息。这种封装不仅隐藏了实现细节,还确保了这些状态的一致性和安全性。

启示与优点
  1. 模块化与扩展性:通过继承和实现接口,Db2SnapshotChangeEventSource展示了如何在保持核心功能不变的情况下,扩展特定于DB2的功能。这启示我们在设计系统时,应该优先考虑模块化和可扩展性,以便于未来对新数据库类型的支持。

  2. 代码复用:继承自通用基类意味着大量的通用逻辑无需重复编写,提高了代码的复用率,减少了错误的可能性,同时也简化了维护工作。

  3. 清晰的责任划分:每个方法都有明确的职责,如preparelockTablesForSchemaSnapshot等,这使得代码易于理解和维护。这强调了在面向对象设计中,清晰地定义类和方法的职责是非常重要的。

  4. 事务与资源管理:类中对事务和资源(如数据库连接)的管理体现了良好的资源管理实践,确保了在异常情况下也能正确释放资源,避免了资源泄露。


总结

提示:Db2SnapshotChangeEventSource 类通过上述核心功能点,实现了对 DB2 数据库的快照捕获与事件转换,为下游系统提供了实时的数据同步能力,确保数据的一致性和实时性。这对于实时数据处理、数据仓库更新以及数据同步等场景具有重要意义。

  • 19
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值