DBus数据库表结构变更处理方案

8次阅读

共计 2875 个字符,预计需要花费 8 分钟才能阅读完成。

导读:DBus 是我们要介绍的在敏捷大数据(Agile BigData)背景下的第一个平台。企业中大量业务数据保存在各个业务系统数据库中,为同时解决数据同步的一致性和实时性问题,DBus(数据总线)平台应运而生。

DBus 专注于数据的实时采集和实时分发,是一种基于日志的解决方案,同时能够提供消息订阅的方式给下游系统使用。本篇文章主要介绍在 DBus 的设计中,它是如何处理表结构变更及其带来的各种问题的。

数据库表结构变更在软件产品快速迭代过程中是普遍存在的现象,抽取数据库中的数据是 DBus 最重要的功能之一,那么对于数据库中表结构变更及其带来的各种问题,DBus 是如何处理的呢? (本文仅讨论 DBus for Oracle 的实现方案)

贴源输出是 DBus 的基本设计原则之一,通过解析后的数据库日志获取数据转换成 UMS 输出到 Kafka,当表结构发生变更时 DBus 必须能够及时的调整输出 UMS 的结构,以确保和数据库中表结构保持一致,这里有两个问题需要解决:

1)如何感知表结构变更?

2)表结构变更后,新的表结构要如何与 OGG 输出的二进制数据关联?

一、感知表结构变更

对于感知表结构变更,Oracle 已经通过 DDL trigger 为我们提供了很好的支持,接下来我们要考虑的是如何让 DBus 感知到表结构变更? 我们讨论出以下两种方案:

1.1 RPC 方案

在 DDL trigger 中调用 DBus 提供的 REST 服务,将表结构变更事件发送给 DBus。

该方案思路简单容易实现,但也有一些明显的弊端,比如 DBus 需要提供高可用、低延时的 REST 服务,否则可能会使数据库中的 DDL 操作变得缓慢甚至执行出现错误;DBus 的 REST 服务器对有数据实时同步需求的所有数据库都必须开通防火墙策略,这将给 DBus 的部署带来很大的麻烦。

1.2 OGG 实时同步方案

在 DDL trigger 中将表结构变更事件存储到一张 Event 表里,然后通过 OGG 实时的从日志中将数据同步到 Kafka,从而感知表结构变更事件。

该方案实现相对复杂但具有很多优点,比如对数据库的侵入性相对较小,DDL 执行时只是将数据写入到 Event 表中,相对网络通信来说,其延时更低、可靠性更高;更明显的优势是这种方案基于数据库日志实现,能够使用 Event 表的数据,严格的将表结构变更前后的数据区分开。

举例来说,对于表:test 来说,依次执行 insert → alter → insert 三个操作,因为 OGG 读取数据库日志存在延时,如果利用 RPC 方案,可能出现这样的一种情况:DBus REST 服务接收到 alter 事件之后,第一个 insert 的记录才被 OGG 捕获并发送给 DBus,此时 DBus 会认为这条数据中包含 alter 变化后的数据。这是一个很严重的问题,而 OGG 实时同步方案无论数据还是时间均通过 OGG 读取日志的方案实现,可以完美的避免这种问题的发生。

对比两种方案 OGG 实时同步方案优势明显,最终我们采用此方案。

然而,采用这种方案也并非一帆风顺,按照该方案的总体思路实现以后,我们遇到了一个很奇怪的问题:通过 DDL trigger 写到 Event 表中的数据无法被 OGG 读取,在经历多番尝试无解之后,我们试图到 OGG 的文档中寻找答案,而最终的结果却是:DML or DDL operations performed from within a DDL trigger are not captured.

这个答案让问题变得更棘手,但这是最佳方案,我们没有理由放弃。于是我们开始尝试在 DDL trigger 中调用存储过程,在存储过程中执行 Event 表的 insert 操作,但由于存储过程和 DDL trigger 仍然属于同一个事务,因此 Event 表的数据依然不能被 OGG 捕获,但通过这个尝试我们觉得只要在另外一个事务中写 Event 表就能解决我们面临的问题,于是我们又想到了 RPC,但 RPC 缺点太过明显。那么有没有其他可以替代的方案呢?

实际上 oracle 数据库里可以使用多种语言来编写存储过程,Oracle 8i 开始支持 java 编写存储过程,于是我们立即开始实现 java 存储过程,通过 JDBC 连接数据库实现 Event 表的写入并提交事务,最终通过实践验证了这种办法的可行性,OGG 成功的获取到了 DDL trigger 调用 java 存储过程写入到 Event 表的数据。

然而,这种实现并不算完美。当我们在生产环境部署 DDL trigger 的时候,发现数据库服务器中并没有安装执行 java 所需要的组件,每次部署都需要 DBA 同学安装执行 java 存储过程所需要的组件,我们试图找到一个不使用 java 存储过程的方案。这里要感谢韩锋老师对我们的帮助,韩老师在听了我 们的实现原理之后,启发我们自治事务应该可以解决这个问题,我们即刻动手开始改造 DDL trigger,使之支持自治事务,经过改造之后该方案才算完美,最终实现逻辑如图 1 所示:

二、处理表结构变更事件

DBus 已经具备通过事件方式感知表结构变更的能力,接下来详细说明一下表结构变更事件该如何处理。

下图描述了 Event 的完整处理流程:

Event 中描述了发生结构变更的表名、该表所属的 schema 以及元数据版本号,DBus 接受并解析 Event 之后,根据表名、schema 以及版本号调用元数据抓取模块获取该表的元数据(包括表的字段类型、长度以及注释等)信息,实际上 DDL trigger 和 alter 语句在一个事务中执行,这样在 trigger 执行过程中无法从 oracle 的数据字典里获取到修改之后表结构元数据,我们写入到 meta_history 表中的元数据只是执行 alter 语句之前的元数据信息(因此我们给这个表取名为 table_meta_his), 要得到完整的元数据信息需要联合 table_meta_his 和数据字典进行查询,示意 SQL 如下:

这个 SQL 的结果有两种可能:

1)只包含 all_tab_cols 视图中的数据

2)既包含 all_tab_cols 视图中的数据又包含 table_meta_his 表的数据(is_current 字段的作用是区别该字段的来源)

结果 A 表明在 table_meta_his 表中没有找到数据,这说明在生成表结构变更 Event 至元数据抓取程序成功获取元数据期间没有再次发生表结构变更,结果 B 则说明在此期间又发生过一次或多次表结构变更。

为什么要使用 union all?

单独使用上图中的两个 SQL 可能导致元数据获取程序获取到错误的结果,例如:接到表结构变更 Event 1 后,我们调用 SQL 1 查询 table_meta_his 结果集为空,在调用 SQL 2 之前表结构再次发生变更(命名为 Event 2),这种情况下我们通过 SQL 2 查询到的结果实际上是再次变更后的结果,使用这个结果产生的元数据去解析 Event 1 和 Event 2 之间的数据,如果两次表结构变更是不兼容的,那么必然会导致解析失败。

感知表结构变更以及处理表结构变更事件的最终目的是能够生成正确的输出结果,其中的更多细节以及实现可以参考:

https://github.com/BriData/DBus

作者:张玉峰

来源:宜信技术学院

正文完
 0