4. 元数据采集技术:JDBC/ODBC采集、日志解析、API对接、爬虫技术

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

这一章,我把四种主流采集技术掰开揉碎讲清楚。每种技术都有它的脾气,选对了事半功倍,选错了天天加班修管道。

4.1 JDBC/ODBC采集:最传统,也最稳

JDBC和ODBC,是关系型数据库的「标准接口」。你想想看,MySQL、Oracle、SQL Server、PostgreSQL,它们都支持JDBC。所以用JDBC采集元数据,是最通用的方案。

具体怎么干?其实就是连上数据库,执行系统表或信息视图的查询。比如MySQL里查 information_schema.TABLES 和 information_schema.COLUMNS,Oracle里查 ALL_TAB_COLUMNS。

核心采集内容:
  • 表名、表注释、表类型(表/视图)
  • 字段名、字段类型、字段长度、是否为空、默认值
  • 主键、外键、索引信息
  • 分区信息、存储引擎、行数估算

给你看一段我常用的JDBC采集代码(Java版):

// 获取所有表信息
DatabaseMetaData metaData = connection.getMetaData();
ResultSet tables = metaData.getTables(
    catalog, schemaPattern, "%", new String[]{"TABLE", "VIEW"});

while (tables.next()) {
    String tableName = tables.getString("TABLE_NAME");
    String tableType = tables.getString("TABLE_TYPE");
    String remarks = tables.getString("REMARKS");
    
    // 获取字段信息
    ResultSet columns = metaData.getColumns(
        catalog, schemaPattern, tableName, "%");
    while (columns.next()) {
        String colName = columns.getString("COLUMN_NAME");
        int dataType = columns.getInt("DATA_TYPE");
        String typeName = columns.getString("TYPE_NAME");
        int colSize = columns.getInt("COLUMN_SIZE");
        boolean nullable = columns.getInt("NULLABLE") == 1;
    }
}
我曾经踩过的坑:

有一次采集Oracle的元数据,getTables() 返回了上万个临时表。原因是schema参数传错了,导致把整个实例的表都扫了一遍。那次采集跑了6个小时,还把源库的CPU打到了90%。后来我加了两层过滤:第一层按schema白名单,第二层按表名正则排除临时表。

JDBC采集的优缺点很明显:

优点 缺点
标准化接口,兼容性好 对源库有性能影响(尤其大库)
能采集到细粒度的字段级信息 无法采集非结构化数据
实现简单,JDBC驱动现成 大数据组件(Hive、HBase)支持差
支持事务,可做增量采集 网络延迟高时效率低
我的建议:

JDBC采集适合OLTP类数据库,比如业务系统的MySQL、Oracle。对于OLAP或大数据组件,别死磕JDBC,后面几种技术更合适。

4.2 日志解析:采得深,但费功夫

日志解析,是从数据库的日志文件里提取元数据变更信息。比如MySQL的binlog、Oracle的redo log、PostgreSQL的WAL。为什么要搞这么复杂?因为有些场景下,你不仅要知道表结构长什么样,还要知道它什么时候变的、怎么变的。

我举个例子。你有一个核心业务表,每天被几十个任务读写。突然有一天,某个字段的类型从 int 改成了 varchar,导致下游ETL全挂了。如果你只做全量采集,可能第二天才发现。但如果你解析了binlog,变更发生的那一刻就能捕获到。

日志解析的典型流程:

  1. 日志订阅:连接到数据库的日志流,比如MySQL的binlog dump协议
  2. 事件解析:解析日志事件,识别DDL(ALTER、CREATE、DROP)和DML(INSERT、UPDATE、DELETE)
  3. 元数据提取:从DDL语句中提取变更后的表结构
  4. 版本管理:记录每次变更的时间戳和版本号

我用Canal做过MySQL的实时元数据采集,核心代码大概这样:

// Canal连接配置
CanalConnector connector = CanalConnectors.newSingleConnector(
    new InetSocketAddress("127.0.0.1", 11111),
    "destination", "username", "password");

connector.connect();
connector.subscribe(".*\\..*");

while (running) {
    Message message = connector.getWithoutAck(100);
    long batchId = message.getId();
    
    for (CanalEntry.Entry entry : message.getEntries()) {
        if (entry.getEntryType() == CanalEntry.EntryType.ROWDATA) {
            CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(
                entry.getStoreValue());
            
            // 识别DDL事件
            if (rowChange.getIsDdl()) {
                String ddlSql = rowChange.getSql();
                String schemaName = entry.getHeader().getSchemaName();
                String tableName = entry.getHeader().getTableName();
                // 解析DDL,提取新表结构
                processDDL(schemaName, tableName, ddlSql);
            }
        }
    }
    connector.ack(batchId);
}
注意:

日志解析对运维要求很高。binlog的格式(ROW/STATEMENT/MIXED)会影响解析结果,日志的保留时长、磁盘空间、主从切换都会导致采集中断。我建议在生产环境先做压力测试,别一上来就全量订阅。

4.3 API对接:最优雅,但得看对方脸色

API对接,是通过数据源提供的RESTful或RPC接口来获取元数据。现在很多云服务和SaaS平台都提供了元数据API,比如AWS Glue、阿里云DataWorks、Snowflake的INFORMATION_SCHEMA。

为什么说它优雅?因为API是官方支持的,不会像JDBC那样有性能风险,也不会像日志解析那样依赖底层实现。你只需要调接口、解析JSON,就能拿到结构化的元数据。

但问题也很明显——每个平台的API都不一样。你对接了Snowflake,换到BigQuery就得重写一套。我整理过一份主流平台的元数据API对比:

平台 API类型 采集内容 限流策略
Snowflake SQL + REST 表、视图、字段、标签、策略 按仓库大小限流
AWS Glue REST (GetTables, GetPartitions) 表、分区、存储格式、位置 每秒1000次
Databricks REST + Unity Catalog 表、列、权限、血缘 按工作区限流
Tableau REST (Metadata API) 数据源、工作表、字段、计算 每小时5000次

写一个简单的API调用示例(Python):

import requests
import json

def fetch_snowflake_metadata(account, , database):
    url = f"https://{account}.snowflakecomputing.com/api/v2/databases/{database}/schemas"
    headers = {
        "Authorization": f"Bearer {}",
        "Content-Type": "application/json"
    }
    
    # 获取所有schema
    response = requests.get(url, headers=headers)
    schemas = response.json()
    
    metadata = []
    for schema in schemas:
        # 获取每个schema下的表
        tables_url = f"{url}/{schema['name']}/tables"
        tables_resp = requests.get(tables_url, headers=headers)
        tables = tables_resp.json()
        
        for table in tables:
            metadata.append({
                "database": database,
                "schema": schema['name'],
                "table": table['name'],
                "columns": table['columns'],
                "row_count": table.get('row_count', 0)
            })
    
    return metadata
我的经验:

API对接一定要处理好限流和重试。我习惯用指数退避策略,第一次失败等1秒,第二次等2秒,第三次等4秒,最多重试5次。另外,API返回的元数据往往是缓存快照,不是实时的。如果你需要秒级感知变更,API可能不够用。

4.4 爬虫技术:最后的武器

爬虫技术,听起来有点「野路子」,但在某些场景下是唯一的选择。比如:

  • 老旧的ERP系统,没有API,也不开放数据库直连
  • Web版的数据管理工具,比如Apache Atlas的Web UI
  • 内部Wiki或文档系统,里面记录了数据字典

说白了,就是模拟人操作浏览器,从页面上抓取元数据。我一般用Selenium或Playwright来做。

给你看一个用Playwright爬取Atlas元数据的例子:

from playwright.sync_api import sync_playwright

def crawl_atlas_metadata(atlas_url, username, password):
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        page = browser.new_page()
        
        # 登录
        page.goto(f"{atlas_url}/login")
        page.fill("#username", username)
        page.fill("#password", password)
        page.click("#login-btn")
        page.wait_for_load_state("networkidle")
        
        # 进入数据资产页面
        page.goto(f"{atlas_url}/search?type=Table")
        page.wait_for_selector(".table-row")
        
        # 提取表列表
        tables = []
        rows = page.query_selector_all(".table-row")
        for row in rows:
            table_name = row.query_selector(".name").inner_text()
            table_type = row.query_selector(".type").inner_text()
            
            # 点击进入详情页
            row.click()
            page.wait_for_selector(".column-list")
            
            columns = []
            col_elements = page.query_selector_all(".column-item")
            for col in col_elements:
                columns.append({
                    "name": col.query_selector(".col-name").inner_text(),
                    "type": col.query_selector(".col-type").inner_text(),
                    "comment": col.query_selector(".col-comment").inner_text()
                })
            
            tables.append({
                "name": table_name,
                "type": table_type,
                "columns": columns
            })
            
            # 返回列表页
            page.go_back()
            page.wait_for_selector(".table-row")
        
        browser.close()
        return tables
我曾经踩过的坑:

爬虫最大的问题是脆弱性。页面结构一变,爬虫就废了。有一次我爬一个内部系统,对方前端框架升级,所有CSS选择器都变了,我花了三天重新适配。后来我改用XPath加正则匹配,稍微抗造一点。但说实话,爬虫只适合做一次性或低频的采集,别把它当主力方案。

4.5 四种技术怎么选?一张图说清楚

下面这张图,是我自己总结的选型框架。你根据数据源的类型、变更频率、性能要求,对号入座就行。

元数据采集技术选型框架 数据源类型 关系型数据库 大数据组件 云服务/SaaS 老旧/无API系统 推荐:JDBC/ODBC 推荐:日志解析 推荐:API对接 推荐:爬虫技术 选型口诀 关系库用JDBC,大数据看日志,云服务调API,老旧系统上爬虫 实际项目中往往是多种技术组合使用,没有银弹

4.6 组合拳才是王道

你可能会问:「那我到底用哪种?」我的答案是——别只押一种。

我做过一个金融客户的项目,他们的数据源包括:

  • 核心交易库(Oracle):用JDBC做全量采集,每天凌晨跑一次
  • 实时风控库(MySQL):用binlog日志解析做实时采集
  • 数仓(Snowflake):用API对接,按需触发
  • 内部数据字典(Confluence):用爬虫每周抓一次

你看,四种技术全用上了。每种技术解决一类问题,组合起来才能覆盖所有场景。

核心原则:
  • 能调API就别爬虫——API稳定、高效、官方支持
  • 需要实时就用日志——JDBC全量采集做不到秒级感知
  • 老旧系统别强求——爬虫虽然丑,但能干活
  • 性能优先选JDBC——简单直接,对开发者友好

嗯,这一章的内容就到这儿。四种技术各有千秋,没有的好坏。关键是你得清楚自己的数据源长什么样、变更频率多高、能接受多大的延迟。想清楚这些,选型就不难了。