提示:
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
来封装快照上下文,包括事务隔离级别、保存点等信息。这种封装不仅隐藏了实现细节,还确保了这些状态的一致性和安全性。
启示与优点
-
模块化与扩展性:通过继承和实现接口,
Db2SnapshotChangeEventSource
展示了如何在保持核心功能不变的情况下,扩展特定于DB2的功能。这启示我们在设计系统时,应该优先考虑模块化和可扩展性,以便于未来对新数据库类型的支持。 -
代码复用:继承自通用基类意味着大量的通用逻辑无需重复编写,提高了代码的复用率,减少了错误的可能性,同时也简化了维护工作。
-
清晰的责任划分:每个方法都有明确的职责,如
prepare
、lockTablesForSchemaSnapshot
等,这使得代码易于理解和维护。这强调了在面向对象设计中,清晰地定义类和方法的职责是非常重要的。 -
事务与资源管理:类中对事务和资源(如数据库连接)的管理体现了良好的资源管理实践,确保了在异常情况下也能正确释放资源,避免了资源泄露。
总结
提示:Db2SnapshotChangeEventSource
类通过上述核心功能点,实现了对 DB2 数据库的快照捕获与事件转换,为下游系统提供了实时的数据同步能力,确保数据的一致性和实时性。这对于实时数据处理、数据仓库更新以及数据同步等场景具有重要意义。