4、元数据采集适配器:插件架构与三种采集模式

元数据采集这件事,说白了就是要把各种数据源里的表结构、字段信息、分区信息给捞出来。听起来简单,但真做起来你会发现——每个数据源的脾气都不一样。

MySQL 用 JDBC 就能搞定,Hive 得走 MetaStore API,Kafka 得解析日志,还有些自研系统连文档都不全。所以我一直觉得,采集框架必须得灵活。怎么灵活?插件化。

4.1 基于插件架构的采集框架

我最早做元数据平台的时候,犯过一个错:把所有采集逻辑写在一个大类里。结果每次加新数据源,都要改核心代码,测试回归一遍,上线还提心吊胆。后来我重构了一次,改成插件架构,世界清净了。

插件架构的核心思路就一句话:定义好接口,让每个数据源自己实现。

核心接口定义(简化版):

public interface MetadataCollector {
    // 采集器初始化
    void init(Map<String, String> config);
    
    // 获取所有数据库/目录
    List<Catalog> listCatalogs();
    
    // 获取指定库下的所有表
    List<Table> listTables(String catalog, String schema);
    
    // 获取表的详细元数据(字段、分区、注释等)
    TableDetail getTableDetail(TableIdentity identity);
    
    // 判断是否支持增量采集
    boolean supportIncremental();
    
    // 增量采集(返回变更时间戳之后的数据)
    List<ChangeEvent> incrementalCollect(long lastTimestamp);
}

每个数据源写一个实现类,打成 jar 包,放到采集器的插件目录下。框架启动时自动扫描加载。这样加新数据源,不需要改一行框架代码。

嗯,这里要注意:插件和框架之间的版本兼容性。我建议用 SPI 机制(ServiceLoader)或者 OSGi,别用 Class.forName 硬加载,否则类冲突够你喝一壶的。

4.2 三种采集模式

根据数据源的类型和开放能力,我把采集模式分成三种。每种都有它的适用场景和坑。

采集模式 适用数据源 优点 缺点
JDBC 模式 MySQL、PostgreSQL、Oracle、SQL Server 标准 SQL,通用性强 大表查询慢,对数据库有压力
API 模式 Hive MetaStore、Glue、Atlas、DataHub 效率高,支持批量 依赖第三方 API 版本
日志模式 Kafka、Flume、自研系统 实时性好,不侵入源系统 解析复杂,需要维护日志格式

4.2.1 JDBC 模式

这是最基础的模式。说白了就是连上数据库,执行 information_schema 或者 SHOW CREATE TABLE 之类的 SQL。

我在项目中遇到过一个问题:某个 MySQL 实例上有 2 万多张表,全量采集一次要跑 40 分钟。后来怎么优化的?分页 + 并行。把库拆成多个线程,每个线程查一部分,时间降到了 8 分钟。

JDBC 采集避坑指南:

  • 连接池一定要配,别每次都新建连接
  • 设置查询超时,防止大表卡死
  • 对于 Oracle,注意 ALL_TAB_COLUMNS 和 DBA_TAB_COLUMNS 的权限区别

4.2.2 API 模式

对于 Hive、Iceberg 这类数据湖组件,直接调 MetaStore API 比 JDBC 快得多。Hive 的 MetaStore Thrift API 可以一次拿到所有表的分区信息,不用一条条查。

我记得有一次对接阿里云的 DataWorks,他们的 API 有频率限制,每秒最多 10 次。我一开始没注意,结果采集到一半被限流了。后来加了重试和退避策略,才稳定下来。

// Hive MetaStore API 示例(伪代码)
val client = new HiveMetaStoreClient(conf)
val databases = client.getAllDatabases()
databases.foreach { db =>
    val tables = client.getAllTables(db)
    tables.foreach { table =>
        val tableObj = client.getTable(db, table)
        // 解析 tableObj 的字段、分区、存储格式等
    }
}

注意:API 模式最怕版本不兼容。Hive 2.x 和 3.x 的 Thrift 接口有差异,升级前一定要做兼容性测试。我曾经因为没注意这个,导致采集任务挂了整整一个周末。

4.2.3 日志模式

有些系统不提供 JDBC 也不提供 API,比如自研的元数据服务。这时候只能从日志里捞。

日志模式的核心是解析。你得知道日志的格式,是 JSON 还是 CSV,字段怎么对齐。我建议统一用 JSON 格式,解析起来最省事。

举个例子,Kafka 里每条消息可能长这样:

{
  "event_type": "CREATE_TABLE",
  "table_name": "orders",
  "schema": "public",
  "columns": [
    {"name": "id", "type": "bigint", "comment": "主键"},
    {"name": "amount", "type": "decimal(10,2)", "comment": "金额"}
  ],
  "timestamp": 1700000000000
}

采集器消费 Kafka 消息,解析后写入元数据存储。这种方式实时性最好,但有个前提——源系统必须把变更事件发到消息队列里。

4.3 增量与全量同步策略

全量采集和增量采集,不是二选一,而是配合使用。

全量采集:第一次接入数据源时,把所有元数据拉一遍。之后定期(比如每天凌晨)跑一次,作为兜底。

增量采集:在全量采集的基础上,监听变更事件,只拉取变化的部分。

我个人的习惯是:全量保底,增量提效。

具体策略如下:

  • 首次接入:全量采集,写入基线数据
  • 日常运行:增量采集,监听变更事件(DDL 日志、API 变更通知等)
  • 定期校验:每天凌晨跑一次全量,和增量数据做对比,发现不一致就修复
  • 异常恢复:如果增量采集断了(比如 Kafka 挂了),恢复后先补全量,再切回增量

增量采集的关键:记录好上次采集的时间戳或偏移量。我建议用数据库表或者 ZooKeeper 来存,别用本地文件,否则节点挂了就丢了。

你想想看,如果没有增量采集,每天全量跑一次,2 万张表的数据量,光网络开销就够呛。有了增量,大部分时间只处理几十张变更的表,效率提升不是一点半点。

4.4 整体架构图

下面这张图展示了采集适配器的整体架构。从插件加载到三种采集模式,再到全量/增量策略的配合,一目了然。

元数据采集适配器架构 插件加载器 (Plugin Loader) MetadataCollector 接口 JDBC 采集器 API 采集器 日志采集器 MySQL / Oracle / PG Hive / Glue / Atlas Kafka / 日志文件 同步策略调度器 全量采集(首次/每日兜底) | 增量采集(实时变更) 元数据存储(MySQL / ES / HBase) 说明:插件加载器通过 SPI 机制加载各采集器实现,采集器根据数据源类型选择 JDBC/API/日志模式, 同步策略调度器根据任务配置决定全量或增量执行,最终将元数据写入统一存储。

4.5 实战中的几个建议

最后,分享几个我在实战中踩过的坑,希望能帮你少走弯路。

  • 插件版本管理:每个采集器 jar 包都要带版本号,框架里做版本兼容检查。我曾经因为插件版本和框架不匹配,导致 ClassNotFoundException,排查了半天。
  • 采集超时处理:JDBC 查询设置 Statement Timeout,API 调用设置 HTTP Timeout。别让一个慢查询拖垮整个采集任务。
  • 增量采集的幂等性:同一个变更事件可能被消费多次,采集器要能去重。我一般用事件 ID + 时间戳做唯一键。
  • 监控告警:采集任务的成功率、延迟、数据量都要上报。如果连续 3 次采集失败,自动告警并切换为全量模式。

一个小技巧:全量采集时,可以先用 COUNT(*) 估算数据量,如果和上次差异太大,先告警再采集。这样可以提前发现数据异常,避免采集到脏数据。