4、元数据采集与同步机制:全量元数据扫描、增量元数据捕获(DDL/DML事件监听)、元数据版本管理

好,咱们进入第四章节。前面聊完了元模型怎么设计,现在得解决一个更实际的问题——元数据到底怎么从源端“搬”到湖里来?

你想想看,一个企业级数据湖,少说对接几十个数据源。MySQL、Oracle、PostgreSQL、Kafka、Hive……每个源的元数据都在不断变化。今天加个字段,明天改个类型,后天删张表。如果元数据采集跟不上,数据湖的“目录”就是错的,下游分析全得翻车。

我个人习惯把元数据采集分成三大块:全量扫描、增量捕获、版本管理。这三者缺一不可。下面我一个个拆开讲。

4.1 全量元数据扫描:先把家底摸清楚

全量扫描,说白了就是第一次对接数据源时,把它的所有元数据一口气拉过来。这步是基础,基础不牢,地动山摇。

核心目标:建立数据源元数据的完整基线快照。

我在项目中遇到过最典型的场景:新接入一个Oracle数据仓库,里面有2000多张表,上百个视图,还有一堆物化视图和同义词。如果不做全量扫描,你根本不知道湖里该建哪些目录。

全量扫描通常分四步走:

  1. 连接探测——验证数据源可达性,获取数据库版本、字符集等基础信息。
  2. Schema枚举——遍历所有Schema/数据库,排除系统Schema(比如MySQL的mysql、information_schema)。
  3. 对象清单拉取——对每个Schema,拉取表、视图、函数、存储过程等对象的元数据。
  4. 字段级详情采集——对每张表,采集字段名、类型、长度、精度、默认值、注释、主键、索引等。

这里有个坑,我曾经踩过——大表字段数过多导致OOM。有一次扫描一张2000多列的宽表,采集程序直接内存溢出了。后来我加了个分页拉取机制,每次只拉100个字段,分批写入元数据存储。

我的建议:全量扫描一定要支持断点续传。扫描到一半网络断了,下次能从断点继续,而不是从头再来。尤其是几千张表的大库,重扫一次可能要几个小时。

全量扫描的触发时机,我一般设三个:

  • 首次接入——必须全量。
  • 定期全量校准——比如每周日凌晨跑一次,用于修复增量采集可能遗漏的变更。
  • 手动触发——运维人员怀疑元数据不一致时,一键触发。

4.2 增量元数据捕获:监听每一次变化

全量扫描搞定了基线,但数据源每天都在变。如果每次变更都全量扫一遍,性能扛不住,也没必要。这时候就需要增量捕获。

增量捕获的核心是监听两类事件:DDL(数据定义语言)和DML(数据操作语言)。注意,这里的DML不是指数据行变更,而是指元数据相关的DML——比如ALTER TABLE修改了字段注释,或者RENAME TABLE改了表名。

4.2.1 DDL事件监听

DDL事件是元数据变更的主战场。常见的DDL包括:

DDL类型 影响范围 采集策略
CREATE TABLE 新增表 全字段采集
ALTER TABLE ADD COLUMN 新增字段 只采集新增字段
ALTER TABLE MODIFY COLUMN 修改字段类型/注释 更新字段元数据
DROP TABLE 删除表 标记软删除,保留历史
RENAME TABLE 表名变更 更新表名,保留旧名映射
TRUNCATE TABLE 清空表 记录操作时间,不影响元数据

不同数据源的DDL捕获方式不一样。我列几个常见的:

  • MySQL:开启binlog,监听ROW格式的DDL事件。或者用INFORMATION_SCHEMA轮询对比。
  • Oracle:使用LogMiner或GoldenGate捕获DDL。Oracle的DDL触发器和DBA_SOURCE也能用,但性能开销大。
  • PostgreSQL:开启logical replication,监听pg_logical插件的DDL事件。或者用事件触发器。
  • Hive:通过Hive Metastore的NotificationLog监听DDL。Hive 3.0以上支持得比较好。

注意:不是所有数据源都原生支持DDL事件推送。比如早期的MySQL版本,DDL在binlog里记录得不完整。我建议不要完全依赖事件监听,最好配合定期增量轮询做兜底。

4.2.2 DML事件监听(元数据维度)

嗯,这里要特别说明一下。我们说的DML事件监听,在元数据管控语境下,不是监听数据行怎么变,而是监听那些通过DML语句间接影响元数据的操作。比如:

  • INSERT INTO ... VALUES——如果表是自描述格式(比如Parquet的schema embedded),新写入的数据可能携带新的字段。
  • UPDATE——某些系统用UPDATE来修改元数据注释(比如Hive的ALTER TABLE ... SET TBLPROPERTIES底层其实是UPDATE)。

不过说实话,纯DML事件监听在元数据采集里用得不多。更常见的做法是结合CDC工具(比如Debezium、Canal)统一捕获数据变更,然后从中提取元数据变更信号。

我曾经在一个实时数仓项目里,用Debezium监听MySQL的binlog,同时捕获DDL和DML。DDL事件直接触发元数据更新,DML事件则用来统计表的数据新鲜度——比如“这张表上次有数据写入是什么时候”。这个信息对数据湖的生命周期管理很有用。

4.3 元数据版本管理:让每一次变更都可追溯

元数据采集回来了,但问题来了——怎么知道元数据什么时候变的?变之前是什么样?

这就是版本管理要解决的问题。没有版本管理,元数据就是一锅粥。你永远不知道当前看到的元数据是不是最新的,也不知道昨天跑的任务用的是哪个版本的元数据。

核心原则:每一次元数据变更,都必须生成一个新版本。旧版本不能覆盖,只能追加。

我常用的版本管理模型是这样的:

元数据版本 = (数据源ID, 对象ID, 版本号, 快照内容, 变更时间, 变更类型, 变更摘要)

版本号规则:从1开始递增,每次变更+1
快照内容:全量JSON序列化,包含该对象的所有元数据字段
变更类型:CREATE / ALTER / DROP / RENAME
变更摘要:描述这次变更了什么(比如“新增字段age, 类型int”)

举个例子,一张表user的版本历史可能是这样的:

版本 变更时间 变更类型 变更摘要
v1 2024-01-01 00:00:00 CREATE 创建表 user,字段:id(int), name(varchar)
v2 2024-01-10 08:30:00 ALTER 新增字段 email(varchar)
v3 2024-01-15 14:20:00 ALTER 修改字段 name 类型 varchar(100) -> varchar(200)
v4 2024-02-01 09:00:00 DROP 删除字段 email

有了版本管理,你能做很多事:

  • 时间旅行查询——比如“2024年1月20日这张表的字段结构是什么样的?”直接查v3版本。
  • 变更回溯——出问题了,看看是哪个版本引入的变更。
  • 数据血缘关联——ETL任务在某个时间点读取了某张表,可以关联到当时的元数据版本。

我的经验:版本快照不要存全量差异,要存全量快照。虽然存储开销大一点,但查询时不用做差异合并,性能好很多。存储成本现在很便宜,别省这个钱。

4.4 整体采集架构

最后,我把全量扫描、增量捕获、版本管理串起来,画一张架构图。这张图是我在多个项目里沉淀下来的通用模式。

元数据采集与同步架构图 元数据采集与同步架构 数据源层 MySQL Oracle PostgreSQL Hive Kafka 采集层 全量扫描引擎 定时触发 / 手动触发 增量捕获引擎 DDL/DML事件监听 版本管理引擎 版本号生成 / 快照存储 元数据存储层 元数据版本库(全量快照) 当前元数据视图 消费层:数据目录 / 血缘分析 / 数据治理

这张图的核心逻辑是:数据源层的元数据变更,通过全量扫描和增量捕获两条路径进入采集层。采集层内部,全量扫描引擎和增量捕获引擎各自产出变更事件,统一交给版本管理引擎生成新版本。版本管理引擎把全量快照写入元数据版本库,同时更新当前元数据视图供下游消费。

这里有个设计要点——全量扫描和增量捕获是互补关系,不是替代关系。增量捕获负责实时性,全量扫描负责兜底和校准。我见过一些团队只做增量捕获,结果某个DDL事件丢了,元数据就永远对不上了。最后还得靠全量扫描来修复。

避坑指南:我曾经在一个项目里,全量扫描和增量捕获同时跑,结果两个线程同时更新同一张表的元数据,导致版本号冲突。后来我加了个分布式锁,按(数据源ID, 对象ID)加锁,确保同一时间只有一个线程在更新某个对象的元数据。