4、元数据采集与同步机制:全量元数据扫描、增量元数据捕获(DDL/DML事件监听)、元数据版本管理
好,咱们进入第四章节。前面聊完了元模型怎么设计,现在得解决一个更实际的问题——元数据到底怎么从源端“搬”到湖里来?
你想想看,一个企业级数据湖,少说对接几十个数据源。MySQL、Oracle、PostgreSQL、Kafka、Hive……每个源的元数据都在不断变化。今天加个字段,明天改个类型,后天删张表。如果元数据采集跟不上,数据湖的“目录”就是错的,下游分析全得翻车。
我个人习惯把元数据采集分成三大块:全量扫描、增量捕获、版本管理。这三者缺一不可。下面我一个个拆开讲。
4.1 全量元数据扫描:先把家底摸清楚
全量扫描,说白了就是第一次对接数据源时,把它的所有元数据一口气拉过来。这步是基础,基础不牢,地动山摇。
核心目标:建立数据源元数据的完整基线快照。
我在项目中遇到过最典型的场景:新接入一个Oracle数据仓库,里面有2000多张表,上百个视图,还有一堆物化视图和同义词。如果不做全量扫描,你根本不知道湖里该建哪些目录。
全量扫描通常分四步走:
- 连接探测——验证数据源可达性,获取数据库版本、字符集等基础信息。
- Schema枚举——遍历所有Schema/数据库,排除系统Schema(比如MySQL的
mysql、information_schema)。 - 对象清单拉取——对每个Schema,拉取表、视图、函数、存储过程等对象的元数据。
- 字段级详情采集——对每张表,采集字段名、类型、长度、精度、默认值、注释、主键、索引等。
这里有个坑,我曾经踩过——大表字段数过多导致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 整体采集架构
最后,我把全量扫描、增量捕获、版本管理串起来,画一张架构图。这张图是我在多个项目里沉淀下来的通用模式。
这张图的核心逻辑是:数据源层的元数据变更,通过全量扫描和增量捕获两条路径进入采集层。采集层内部,全量扫描引擎和增量捕获引擎各自产出变更事件,统一交给版本管理引擎生成新版本。版本管理引擎把全量快照写入元数据版本库,同时更新当前元数据视图供下游消费。
这里有个设计要点——全量扫描和增量捕获是互补关系,不是替代关系。增量捕获负责实时性,全量扫描负责兜底和校准。我见过一些团队只做增量捕获,结果某个DDL事件丢了,元数据就永远对不上了。最后还得靠全量扫描来修复。
避坑指南:我曾经在一个项目里,全量扫描和增量捕获同时跑,结果两个线程同时更新同一张表的元数据,导致版本号冲突。后来我加了个分布式锁,按(数据源ID, 对象ID)加锁,确保同一时间只有一个线程在更新某个对象的元数据。