4、元数据采集与接入:元数据采集策略、JDBC/API采集方式、日志采集与实时同步
元数据采集,说白了就是给数据资产做「人口普查」。你连自己家里有多少张表、字段怎么定义的、数据从哪里来的都不知道,后面的治理、血缘、质量全是空谈。我见过太多团队,一上来就搞数据治理平台,结果连最基本的表结构都没采全,最后项目烂尾。
这一章,咱们就聊聊怎么把元数据「搬」进目录里。我会把几种主流采集方式掰开揉碎讲清楚,包括各自的适用场景、技术细节和我在项目中踩过的坑。
核心观点:没有完美的采集方式,只有最适合你场景的策略。别想着一种方案打天下。
4.1 元数据采集策略:先想清楚再动手
采集策略,说白了就是「什么时候采、采多少、怎么采」。很多团队一上来就写代码连数据库,结果发现采回来的数据要么不全,要么重复,要么把源库搞挂了。
我一般把采集策略分成三个维度来考虑:
- 全量采集 vs 增量采集:第一次跑全量,后面只跑增量。但要注意,有些系统不支持增量,比如老旧的 MySQL 5.5 没有 information_schema 的变更时间戳。
- 定时采集 vs 事件触发:定时采集简单,但会有延迟。事件触发能实时感知,但需要改造数据源。
- 推模式 vs 拉模式:数据源主动推过来,还是我们去拉?推模式对源库压力小,但需要数据源配合。
我的经验:刚开始做元数据采集时,别追求实时。先每天凌晨跑一次全量,跑两周把数据质量稳定下来,再考虑增量。我曾经有个项目,一上来就搞实时 CDC,结果 Binlog 解析出了问题,把生产库的 IO 打满了,差点出事故。
4.2 JDBC 采集方式:最传统也最可靠
JDBC 采集,就是通过数据库的 JDBC 驱动,直接连上去查系统表。比如 MySQL 的 information_schema、Oracle 的 ALL_TABLES、PostgreSQL 的 pg_catalog。
这种方式的好处是:通用、稳定、不需要在数据源上装 Agent。坏处是:对源库有性能影响,而且只能采到结构信息,采不到血缘和热度。
下面是一个典型的 JDBC 采集代码片段,我习惯用 Spring JDBC Template 封装一层:
// MySQL 元数据采集示例
public class MetadataCollector {
private JdbcTemplate jdbcTemplate;
// 采集所有表信息
public List<TableMeta> collectTables(String schema) {
String sql = "SELECT TABLE_NAME, TABLE_COMMENT, ENGINE, " +
"CREATE_TIME, UPDATE_TIME " +
"FROM information_schema.TABLES " +
"WHERE TABLE_SCHEMA = ?";
return jdbcTemplate.query(sql, new Object[]{schema},
(rs, rowNum) -> {
TableMeta meta = new TableMeta();
meta.setTableName(rs.getString("TABLE_NAME"));
meta.setComment(rs.getString("TABLE_COMMENT"));
meta.setEngine(rs.getString("ENGINE"));
meta.setCreateTime(rs.getTimestamp("CREATE_TIME"));
meta.setUpdateTime(rs.getTimestamp("UPDATE_TIME"));
return meta;
});
}
// 采集字段信息
public List<ColumnMeta> collectColumns(String schema, String table) {
String sql = "SELECT COLUMN_NAME, DATA_TYPE, COLUMN_TYPE, " +
"IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT, " +
"CHARACTER_MAXIMUM_LENGTH " +
"FROM information_schema.COLUMNS " +
"WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? " +
"ORDER BY ORDINAL_POSITION";
return jdbcTemplate.query(sql, new Object[]{schema, table},
(rs, rowNum) -> {
ColumnMeta meta = new ColumnMeta();
meta.setColumnName(rs.getString("COLUMN_NAME"));
meta.setDataType(rs.getString("DATA_TYPE"));
meta.setColumnType(rs.getString("COLUMN_TYPE"));
meta.setNullable("YES".equals(rs.getString("IS_NULLABLE")));
meta.setDefaultValue(rs.getString("COLUMN_DEFAULT"));
meta.setComment(rs.getString("COLUMN_COMMENT"));
meta.setMaxLength(rs.getLong("CHARACTER_MAXIMUM_LENGTH"));
return meta;
});
}
}
注意:JDBC 采集时,一定要控制并发数。我见过有人开 50 个线程去查 information_schema,直接把 MySQL 的元数据锁打满了,导致业务查询全部阻塞。建议单数据源并发不超过 5 个线程,并且加上限流。
4.3 API 采集方式:更灵活但更依赖
有些系统不让你直连数据库,或者数据库表结构太复杂,这时候就得走 API。比如 Hive Metastore 的 Thrift API、Kafka 的 Admin API、AWS Glue 的 REST API。
API 采集的好处是:对源系统无侵入,而且能拿到比 JDBC 更丰富的信息(比如表的访问热度、分区信息、数据量估算)。坏处是:每个系统都要单独写适配器,维护成本高。
我整理了一个对比表格,方便你选型:
| 采集方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| JDBC 直连 | 通用性强、实现简单、无需改造 | 性能影响、只能采结构信息 | 关系型数据库、传统数仓 |
| REST API | 无侵入、信息丰富、可扩展 | 每个系统单独适配、依赖 API 稳定性 | 云服务、SaaS 平台、大数据组件 |
| Thrift/gRPC | 高性能、强类型、支持双向流 | 需要生成客户端代码、调试困难 | Hive Metastore、Kafka、内部中间件 |
| SDK/Agent | 功能最全、可采集运行时信息 | 需要安装部署、版本兼容问题 | 需要深度监控、血缘追踪的场景 |
4.4 日志采集与实时同步
前面讲的 JDBC 和 API,都是「拉」的模式。但有些场景下,你需要「推」的模式——数据源主动把变更推给你。这就是日志采集和实时同步要做的事。
4.4.1 日志采集:从 Binlog 到元数据事件
日志采集的核心思路是:监听数据源的变更日志,解析出元数据变更事件,然后同步到元数据目录。最典型的就是 MySQL 的 Binlog。
举个例子,当你在 MySQL 里执行 ALTER TABLE 时,Binlog 里会记录这条 DDL 语句。我们通过 Canal 或 Debezium 解析 Binlog,就能实时感知到表结构的变化。
// 基于 Debezium 的 MySQL DDL 监听示例
public class DdlEventListener {
private final MetadataSyncService syncService;
@EventListener
public void onDdlEvent(DdlEvent event) {
// 解析 DDL 语句
String database = event.getDatabase();
String table = event.getTable();
String ddl = event.getDdl();
// 提取变更后的表结构
TableMeta newMeta = parseDdl(database, table, ddl);
// 同步到元数据目录
syncService.syncTableMeta(database, table, newMeta);
// 记录变更历史
syncService.recordChangeHistory(database, table, ddl);
}
private TableMeta parseDdl(String database, String table, String ddl) {
// 这里可以用 SQL 解析器(如 JSqlParser)解析 DDL
// 提取字段、索引、分区等信息
// 实际项目中建议用专门的 DDL 解析库
return new TableMeta();
}
}
避坑指南:我曾经在解析 Binlog 时,遇到一个坑——MySQL 8.0 的 Binlog 格式和 5.7 不一样,DDL 事件的序列化方式变了。升级数据库版本后,解析程序直接报错。所以,日志采集一定要做好版本兼容测试,最好在解析层做一层适配。
4.4.2 实时同步:从 Kafka 到元数据湖
实时同步,说白了就是把元数据变更当成数据流来处理。架构一般是:数据源 → CDC 工具 → Kafka → 流处理引擎 → 元数据目录。
这种架构的好处是:解耦、可扩展、支持回溯。坏处是:链路长、延迟增加、运维复杂。
我一般建议这样设计实时同步链路:
- 数据源层:MySQL、PostgreSQL、Oracle 等,开启 Binlog 或归档日志
- 采集层:Debezium / Canal / Maxwell,解析日志并发送到 Kafka
- 传输层:Kafka 集群,保证消息不丢失、不重复
- 处理层:Flink / Kafka Streams,做数据清洗、去重、格式转换
- 存储层:元数据目录(如 Atlas、DataHub 或自研平台)
重要提醒:实时同步不是银弹。如果你的数据源超过 100 个,实时链路的运维成本会急剧上升。我见过一个团队,上了 200 个实时同步任务,结果每天光处理数据延迟告警就要花 3 个人力。建议:核心数据源用实时,非核心用定时批量。
4.5 混合采集架构:我推荐的做法
说了这么多,你可能想问:到底该怎么选?我的建议是——混合架构。
具体来说:
- 存量数据:用 JDBC 或 API 做一次全量采集,把历史数据补齐
- 增量变更:核心数据源用日志采集实时同步,非核心用定时任务每天跑一次增量
- 兜底机制:每周跑一次全量校验,发现不一致就重新同步
这样做的好处是:既有实时性,又有可靠性。即使实时链路出了问题,全量校验也能把数据补回来。
总结一句话:元数据采集没有银弹。JDBC 稳、API 灵活、日志实时、混合可靠。根据你的数据源规模、变更频率和团队能力,选最适合的方案。别盲目追求技术先进性,稳定压倒一切。