引言
当一家企业拥有数千张表、数十万字段时,“数据在哪里、谁在用、可信吗"就成了比"怎么算"更难回答的问题。数据目录(Data Catalog)回答"有什么"与"谁负责”,数据血缘(Data Lineage)回答"从哪来、到哪去"。两者合在一起,构成了数据治理的地基:没有目录,数据不可发现;没有血缘,变更不可控。本文将围绕元数据模型、三大开源平台选型、血缘解析技术、治理策略与变更影响分析,给出一个可直接在企业落地的数据目录与血缘体系。
数据目录是"静态的说明书",数据血缘是"动态的流向图",而元数据治理是把两者连接起来的那根线。
一、元数据管理模型
1.1 三类元数据
元数据管理的第一步是区分三类元数据,它们的采集方式、更新频率与消费场景完全不同。
| 类别 | 英文 | 来源 | 示例 | 更新频率 |
|---|---|---|---|---|
| 技术元数据 | Technical | 数据源系统 Catalog | schema、类型、分区、存储位置 | 实时/分钟级 |
| 业务元数据 | Business | 业务团队与治理流程 | 中文描述、Owner、业务口径 | 按发布节奏 |
| 操作元数据 | Operational | 调度与运行系统 | 作业耗时、质量分、运行状态 | 每次运行 |
1.2 元数据模型设计的核心问题
设计元数据模型时,以下三个问题决定了目录能否长期可用:
- 资产粒度:表级还是字段级?字段级血缘才支撑真正的根因分析,但采集成本更高。
- 唯一标识(URN):跨平台资产需要稳定、可寻址的标识,例如
urn:li:dataset:(urn:li:dataPlatform:snowflake,warehouse.ods.orders,PROD)。 - 增量 vs 全量:元数据量大后必须支持增量采集与版本化,避免每次全量扫描打爆源库。
1.3 元数据模型示例
以字段级元数据为例,一个字段需要同时承载技术属性与治理属性。
{
"asset": "warehouse.ods.orders",
"field": "order_id",
"urn": "urn:li:dataset:(urn:li:dataPlatform:postgres,warehouse.ods.orders,PROD)#order_id",
"technical": {
"data_type": "varchar(64)",
"nullable": false,
"partitioned": false
},
"business": {
"description": "订单唯一编号,由订单中心生成",
"owner": "order-squad",
"business_glossary": "term://order/order_id"
},
"governance": {
"classification": "PII-LOW",
"tier": "critical",
"tags": ["order", "core"]
},
"operational": {
"freshness_minutes": 5,
"quality_grade": "A"
}
}
二、开源元数据平台对比
2.1 三大平台定位
OpenMetadata、DataHub 与 Amundsen 是当前三大主流开源元数据平台,选型需结合团队规模与治理深度。
| 平台 | 血缘能力 | 数据质量 | 治理/策略 | 部署形态 | 社区活跃度 |
|---|---|---|---|---|---|
| OpenMetadata | 字段级,SQL 解析内置 | 内置 DQ 服务 | 内置 Data Quality 与分类分级 | Docker / K8s | 高 |
| DataHub | 字段级,多采集源 | 通过插件集成 | 权限、标签、业务词汇表 | Docker / K8s | 高 |
| Amundsen | 表级为主 | 无内置 | 偏向搜索与发现 | 服务组件多 | 中 |
2.2 DataHub 采集 Recipe
DataHub 以 Recipe(YAML)声明采集源,一次可以编排多个 Source。下面同时采集 Postgres 元数据与 dbt manifest。
# datahub_recipe.yaml
source:
type: postgres
config:
host_port: dw-host:5432
database: warehouse
username: datahub
password: ${DATAHUB_DB_PASSWORD}
schema_pattern:
allow: ["ods", "dim", "dws"]
profiling:
enabled: true
# dbt 模型的上游血缘通过 manifest.json 自动构建
source2:
type: dbt
config:
manifest_path: ./target/manifest.json
catalog_path: ./target/catalog.json
node_names_pattern:
allow:
- "model.*"
运行采集:
datahub ingest -c datahub_recipe.yaml
# 或者以增量模式调度
datahub ingest -c datahub_recipe.yaml --dry-run
2.3 OpenMetadata 采集配置
OpenMetadata 使用 JSON 配置驱动 metadata ingest,并原生支持数据质量(Data Quality)与分类(Classification)。
{
"source": {
"type": "mysql",
"serviceName": "warehouse_mysql",
"serviceConnection": {
"config": {
"type": "Mysql",
"hostPort": "dw-host:3306",
"username": "openmetadata",
"password": "${OPENMETADATA_DB_PASSWORD}",
"databaseSchema": "ods"
}
},
"sourceConfig": {
"config": {
"type": "DatabaseMetadata",
"includeTags": true,
"sampleDataCount": 100
}
}
},
"sink": {
"type": "metadata-rest",
"config": {}
}
}
metadata ingest -c openmetadata_mysql.json
metadata profile -c openmetadata_mysql.json # 字段画像
2.4 Amundsen 采集(Databuilder)
Amundsen 通过 Python 的 databuilder 包逐层抽取元数据。
# amundsen_ingest.py
from amundsen.databuilder.extractor.mysql_extractor import MySQLTableExtractor
from amundsen.databuilder.transformer.table_metadata_transformer import TableMetadataTransformer
from amundsen.databuilder.publisher.neo4j_csv_publisher import Neo4jCsvPublisher
extractor = MySQLTableExtractor(
connector=MySQLTableExtractor.CONNECTION_STRING_KEY,
database="warehouse",
table_names=["ods_orders"],
)
transformer = TableMetadataTransformer()
publisher = Neo4jCsvPublisher(
node_files=["table_metadata.csv"],
relationship_files=["table_table.csv"],
neo4j_endpoint="http://neo4j:7474",
)
三、数据资产扫描与分类
3.1 资产发现策略
资产扫描要解决"有哪些数据资产"的问题。推荐策略是:以源系统 Catalog 为基准做首次全量扫描,之后通过 CDC 与心跳机制做增量同步。
| 扫描对象 | 采集内容 | 频率 |
|---|---|---|
| 数据库表/视图 | schema、行数、分区 | 首次全量 + 增量 |
| 湖文件/目录 | 文件格式、路径、分区布局 | 每 30 分钟 |
| 消息 Topic | Schema、消息量、Partition 数 | 实时 |
| 报表/看板 | 依赖的表、Owner | 每次发布 |
3.2 PII 与数据分类
自动分类通常结合规则 + 模型:先用列名/正则识别明显敏感字段,再用 NER 模型兜底。
# classifier.py
import re
PII_RULES = [
(r"(?i)(id_card|ssn|passport)", "PII-HIGH"),
(r"(?i)(phone|mobile|email)", "PII-MEDIUM"),
(r"(?i)(name|address|ip)", "PII-LOW"),
]
def classify_column(col: str, sample_values=None) -> str:
for pattern, level in PII_RULES:
if re.search(pattern, col):
return level
if sample_values:
# 简单指纹:邮箱格式命中即 PII-MEDIUM
if any("@" in v for v in sample_values[:100]):
return "PII-MEDIUM"
return "NONE"
print(classify_column("id_card_no"))
print(classify_column("user_email", ["a@x.com", "b@y.com"]))
3.3 分类分级策略
| 分级 | 定义 | 访问控制建议 |
|---|---|---|
| PUBLIC | 可公开 | 无限制 |
| INTERNAL | 仅内部可见 | 默认可读 |
| CONFIDENTIAL | 敏感业务数据 | 需授权 + 审计 |
| RESTRICTED | 高敏/合规受限(PII、GDPR) | 字段级脱敏 + 审批流 |
四、血缘解析技术
4.1 三种血缘采集方式
血缘的采集不是单一的,而是由多种机制共同拼出完整图谱。
| 方式 | 原理 | 优点 | 局限 |
|---|---|---|---|
| SQL 解析 | 静态解析转换 SQL | 零侵入、字段级 | 复杂 SQL 可能漏判 |
| 日志/事件采集 | 作业运行时上报(OpenLineage) | 真实执行轨迹 | 需埋点改造 |
| 自动化推断 | 基于 dbt/数据管道定义 | 与代码一致 | 依赖工具链路 |
4.2 SQL 解析:字段级血缘
用 sqllineage 对一段典型 ETL SQL 做字段级解析,是最轻量的起点。
# lineage_parse.py
from sqllineage.runner import LineageRunner
sql = """
CREATE TABLE dws.daily_order_revenue AS
SELECT o.order_id,
o.user_id,
o.amount,
u.country
FROM ods.orders o
JOIN dim.users u ON o.user_id = u.user_id
WHERE o.status = 'PAID';
"""
runner = LineageRunner(sql)
for col in runner.get_column_lineage():
print(f"{col.src_columns} -> {col.target_column}")
4.3 SQLGlot 解析复杂语法
sqlglot 擅长解析复杂 SQL 方言,可抽取嵌套子查询中的表依赖。
# sqlglot_deps.py
import sqlglot
ast = sqlglot.parse_one(
"""
WITH paid AS (
SELECT order_id, amount FROM ods.orders WHERE status = 'PAID'
)
SELECT p.order_id, d.channel
FROM paid p
JOIN dim.dim_channel d ON p.order_id = d.order_id
""",
read="snowflake",
)
print(ast.find_all(sqlglot.exp.Table))
4.4 OpenLineage 事件采集
Airflow 等调度器可接入 OpenLineage,将每次作业执行的上下游关系以事件形式推送到血缘服务。
{
"eventType": "COMPLETE",
"job": {
"namespace": "airflow",
"name": "etl_orders_to_dws"
},
"inputs": [
{"namespace": "postgres.warehouse", "name": "ods.orders"},
{"namespace": "postgres.warehouse", "name": "dim.users"}
],
"outputs": [
{"namespace": "snowflake.warehouse", "name": "dws.daily_order_revenue"}
],
"run": {
"runId": "f3c4d5e6-...",
"facets": {"sql": {"query": "INSERT OVERWRITE TABLE dws... "}}
}
}
五、数据发现与搜索
5.1 搜索体验设计
好的数据发现体验应当支持"业务语言 → 资产"的直达路径。元数据平台的搜索通常构建在 Elasticsearch 之上。
| 排序因子 | 权重 | 说明 |
|---|---|---|
| 关键词命中 | 高 | 表名/字段名/描述匹配 |
| 使用热度 | 中 | 近 30 天被查询/引用次数 |
| 质量分 | 中 | 高质量资产优先展示 |
| Owner 在职状态 | 低 | 已离职 Owner 的资产降权 |
5.2 数据源标记与推荐
在 DataHub 中,通过声明式 API 批量标记数据源归属,能显著提升检索命中率。
# mark_data_sources.py
from datahub.emitter.mce_builder import make_dataset_urn
from datahub.emitter.rest_emitter import DatahubRestEmitter
emitter = DatahubRestEmitter("http://datahub-gms:8080")
# 标记表属于 order 数据域
emitter.emit_mcp(
make_dataset_urn("postgres", "warehouse.ods.orders", "PROD"),
aspect={
"type": "globalTags",
"tags": [{"tag": "domain.order"}],
},
)
5.3 业务词汇表(Glossary)
将业务口径沉淀为词汇表(如"订单金额"的定义、同义词与关联字段),让分析师用业务术语直达对应的技术字段,是提升检索命中率与口径统一的关键一环。
六、治理策略:Owner、分级与标签
6.1 Owner 与 Steward 双角色
每个数据集都需要明确业务 Owner(负责口径与使用授权)与数据 Steward(负责日常元数据维护与质量),两者不可缺位。
| 角色 | 职责 | 典型团队 |
|---|---|---|
| Data Owner | 定义业务口径、审批访问、明确 SLA | 业务领域团队 |
| Data Steward | 维护元数据、监控质量、推进治理 | 数据治理团队 |
| Data Consumer | 阅读目录、反馈质量问题 | 分析师/工程师 |
6.2 标签策略落地
标签是治理从"文档"走向"机器可执行"的关键:标签驱动脱敏、合规审批与成本归属。
# tag_policy.yaml
tags:
- name: PII-HIGH
auto_apply:
regex: ["(?i)(id_card|ssn|passport)"]
enforced_actions:
- column_mask: sha256
- require_approval: true
- name: cost_center
auto_apply:
source: dataset.owner.cost_center
enforced_actions:
- allocate_budget: true
6.3 治理的自动化反馈
当治理元数据缺失(例如表没有 Owner)时,目录应自动创建工单并升级提醒,形成"治理闭环"。
# governance_health.py
def audit_governance(datasets: list) -> list:
violations = []
for ds in datasets:
if not ds.get("owner"):
violations.append({"asset": ds["name"], "type": "MISSING_OWNER"})
if ds.get("tier") == "critical" and not ds.get("classification"):
violations.append({"asset": ds["name"], "type": "MISSING_CLASSIFICATION"})
return violations
七、血缘驱动的变更影响分析
7.1 影响分析的语义
血缘图谱是一个有向图,变更影响分析(Impact Analysis)就是在图上做两类遍历:下游影响(改了这张表会波及谁)与上游追溯(这张表的数据从哪来)。
| 变更类型 | 遍历方向 | 决策示例 |
|---|---|---|
| Schema 变更 | 下游 BFS | 通知所有依赖看板与模型 |
| 数据质量下降 | 下游 BFS | 评估受影响指标范围 |
| 数据口径调整 | 上游追溯 | 确认血缘链上的源表 |
| 下线表 | 下游 BFS | 审批前必须确认无消费者 |
7.2 影响分析实现
基于字段级血缘图做广度优先遍历,输出受影响资产的深度与路径。
# impact_analysis.py
from collections import deque
class LineageImpact:
def __init__(self, edges: dict):
self.downstream = edges # node -> [targets]
def impact(self, node: str, max_depth: int = 4) -> list:
result, queue = [], deque([(node, 0)])
seen = {node}
while queue:
cur, d = queue.popleft()
for t in self.downstream.get(cur, []):
if t not in seen:
seen.add(t)
result.append({"asset": t, "depth": d + 1})
if d + 1 < max_depth:
queue.append((t, d + 1))
return result
edges = {
"ods.orders": ["ods.orders_clean", "dws.order_fact"],
"ods.orders_clean": ["dws.order_fact", "dws.daily_revenue"],
"dws.order_fact": ["cube.revenue_cube"],
"cube.revenue_cube": ["dashboards.revenue"],
}
print(LineageImpact(edges).impact("ods.orders"))
7.3 与 CI/CD 联动
将影响分析嵌入数据管道发布流程:发布前自动评估受影响下游,超过阈值则要求审批。
#!/bin/bash
# lineage_gate.sh
IMPACT_COUNT=$(python impact_analysis.py --source ods.orders --max-depth 4 --json | jq 'length')
if [ "$IMPACT_COUNT" -gt 30 ]; then
echo "Impact ${IMPACT_COUNT} assets - requires data owner approval"
exit 1
fi
八、落地案例
8.1 案例:某金融集团元数据治理
某金融集团在 3000+ 张表、20+ 团队的规模下,用 3 个月完成目录与血缘的规模化落地。
| 阶段 | 动作 | 关键结果 |
|---|---|---|
| 选型 | 对比三平台后选 OpenMetadata(内置质量与分类) | 单一平台承载元数据 + DQ |
| 采集 | 全量扫描 + 增量心跳 + Airflow 接入 OpenLineage | 覆盖 92% 生产资产 |
| 治理 | 强制 Owner/分类/分级,缺项自动工单 | 两周补齐 400+ 张核心表 |
| 应用 | 变更影响分析接入发布流程 | 变更事故率下降 65% |
8.2 血缘覆盖率度量
血缘不是"有就行",要度量"关键路径的覆盖率"。
Lineage Coverage Metrics
├── 核心表有血缘的比例 ≥ 95%
├── 关键指标字段级血缘完整度 ≥ 90%
├── 调度作业事件上报率 ≥ 99%
└── 孤儿表(无 Owner 无血缘)数量 持续趋零
九、常见问题与最佳实践
Q1: 血缘图谱会不会太复杂而失控?
会。因此要分层治理:只对 critical/important 层级表强制执行字段级血缘与影响分析,nice_to_have 层级允许表级血缘。同时对血缘图谱定期做"瘦身"——清理已下线资产的节点与边,避免图谱被历史垃圾撑大。
Q2: SQL 解析漏判怎么办?
SQL 解析是"尽力而为",复杂动态 SQL、存储过程会漏。最佳实践是以事件采集(OpenLineage)为主、SQL 解析为辅,两者交叉验证:执行轨迹证明"实际发生了什么",SQL 解析补充"声明上应该发生什么"。
Q3: 目录会不会成为另一个没人维护的系统?
目录最大的敌人是"一次建好、从不更新"。让元数据采集与数据管道部署同步进行(部署即注册),将 Owner/描述缺失接入工单系统自动催办,并把"目录健康度"作为治理团队的 KPI 之一。目录的价值只有持续更新才能兑现。
Q4: 与 Data Mesh 的关系是什么?
Data Mesh 要求每个数据产品可发现、可寻址、可信赖——这恰恰是目录与血缘的职责。在 Mesh 架构中,目录承担联邦发现的注册中心角色,各领域团队通过自助 API 注册自己的数据产品与血缘,中央平台只负责聚合与治理规则。
总结
| 能力 | 推荐工具/方法 | 落地要点 |
|---|---|---|
| 元数据采集 | DataHub Recipe / OpenMetadata ingest | 增量 + 版本化 |
| 血缘采集 | OpenLineage 事件 + SQL 解析 | 事件为主、解析为辅 |
| 分类分级 | 正则 + NER 模型 | 驱动脱敏与授权 |
| 搜索发现 | Elasticsearch 索引 + 词汇表 | 业务语言直达资产 |
| 影响分析 | 血缘图 BFS + CI 联动 | 发布前评估下游 |
| 治理闭环 | Owner 审计 + 自动工单 | 缺项即催办 |
数据目录与血缘不是一次性工程,而是一套持续运转的元数据基础设施。它的落地公式可以概括为:以技术元数据为地基,以业务元数据为语言,以操作元数据为脉搏,以血缘为纽带,让每一次数据变更都"可发现、可追溯、可评估"。 从核心表开始,让目录和血缘真正成为数据团队的导航地图。
参考与延伸阅读
- DataHub 官方文档:Ingestion Recipe、dbt 采集与 SQL Lineage
- OpenMetadata 官方文档:Metadata Ingestion、Data Quality 与 Classification
- OpenLineage 规范:作业运行事件与血缘图谱的数据模型
- Zhamak Dehghani. Data Mesh: Delivering Data-Driven Value at Scale
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。