4、元数据采集与接入:元数据采集策略、JDBC/API采集方式、日志采集与实时同步

元数据采集,说白了就是给数据资产做「人口普查」。你连自己家里有多少张表、字段怎么定义的、数据从哪里来的都不知道,后面的治理、血缘、质量全是空谈。我见过太多团队,一上来就搞数据治理平台,结果连最基本的表结构都没采全,最后项目烂尾。

这一章,咱们就聊聊怎么把元数据「搬」进目录里。我会把几种主流采集方式掰开揉碎讲清楚,包括各自的适用场景、技术细节和我在项目中踩过的坑。

核心观点:没有完美的采集方式,只有最适合你场景的策略。别想着一种方案打天下。

元数据采集策略全景图 元数据采集引擎 策略一:批量采集(定时) JDBC 直连 / API 拉取 适用:关系型数据库、数仓 策略二:日志采集(近实时) Binlog / 审计日志 / 事件流 适用:变更频繁、需感知增量 策略三:实时同步(流式) Kafka / CDC / 消息队列 适用:高时效、跨系统同步 选型建议 • 数据源少于 50 个、变更不频繁 → 批量采集(成本低、实现简单) • 数据源 50-200 个、需要感知变更 → 批量 + 日志混合(推荐) • 数据源超过 200 个、实时性要求高 → 实时同步 + 事件驱动架构 • 我个人的习惯:先批量把存量采完,再上增量,别一上来就搞实时

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 → 流处理引擎 → 元数据目录。

这种架构的好处是:解耦、可扩展、支持回溯。坏处是:链路长、延迟增加、运维复杂。

我一般建议这样设计实时同步链路:

  1. 数据源层:MySQL、PostgreSQL、Oracle 等,开启 Binlog 或归档日志
  2. 采集层:Debezium / Canal / Maxwell,解析日志并发送到 Kafka
  3. 传输层:Kafka 集群,保证消息不丢失、不重复
  4. 处理层:Flink / Kafka Streams,做数据清洗、去重、格式转换
  5. 存储层:元数据目录(如 Atlas、DataHub 或自研平台)

重要提醒:实时同步不是银弹。如果你的数据源超过 100 个,实时链路的运维成本会急剧上升。我见过一个团队,上了 200 个实时同步任务,结果每天光处理数据延迟告警就要花 3 个人力。建议:核心数据源用实时,非核心用定时批量。

4.5 混合采集架构:我推荐的做法

说了这么多,你可能想问:到底该怎么选?我的建议是——混合架构。

具体来说:

  • 存量数据:用 JDBC 或 API 做一次全量采集,把历史数据补齐
  • 增量变更:核心数据源用日志采集实时同步,非核心用定时任务每天跑一次增量
  • 兜底机制:每周跑一次全量校验,发现不一致就重新同步

这样做的好处是:既有实时性,又有可靠性。即使实时链路出了问题,全量校验也能把数据补回来。

总结一句话:元数据采集没有银弹。JDBC 稳、API 灵活、日志实时、混合可靠。根据你的数据源规模、变更频率和团队能力,选最适合的方案。别盲目追求技术先进性,稳定压倒一切。