3. 数据血缘核心概念:节点、边与关系类型

聊到血缘分析,很多人第一反应就是「画个图嘛」。嗯,图确实是血缘的载体,但真正决定血缘系统能不能用的,是图里的三个核心要素:节点、边、以及关系类型。

我记得刚做第一个血缘项目时,团队花了两周把数据流图画出来了,结果业务方一看就摇头:「这图我看不懂,到底哪个表影响了哪个报表?」问题出在哪?就是我们只画了节点和线,没给每条线打上「关系标签」。今天这一章,我就把这三个概念掰开揉碎了讲清楚。

3.1 血缘节点:数据世界的「实体」

节点是血缘图里的基本单元。说白了,它就是数据流转过程中每一个「停下来」的地方。

我习惯把节点分成三类:

节点类型 典型例子 说明
数据源节点 MySQL 表、Kafka Topic、文件 数据的起点,只出不进
计算节点 Spark SQL、Flink 算子、存储过程 数据在这里被加工、转换
数据目标节点 Hive 表、ClickHouse 表、报表 数据的终点,只进不出

关键点:一个节点可以有多个输入和多个输出。比如一张 Hive 宽表,它既是上游多个表的「目标」,又是下游多个报表的「源」。

你在设计节点模型时,一定要给每个节点一个全局唯一的 ID。我见过有人直接用表名当 ID,结果不同库有同名表,血缘图全乱了。我的做法是:数据源类型 + 库名 + 表名,比如 mysql://sales_db/orders。

3.2 血缘边:数据流动的「管道」

边就是节点之间的连线。但这条线不是随便画的——它必须携带元信息。

一条完整的血缘边,至少包含:

  • 源节点 ID:数据从哪来
  • 目标节点 ID:数据到哪去
  • 关系类型:数据依赖还是转换依赖
  • 时间戳:这条血缘关系什么时候建立的
  • 可选属性:字段映射、过滤条件、数据量等

我曾经踩过一个坑:只存了节点和边,没存时间戳。结果数据链路重构后,旧的血缘关系和新的一起显示,整个图乱成一锅粥。从那以后,我每条边都带上 valid_from 和 valid_to,支持时间回溯。

3.3 血缘关系类型:数据依赖 vs 转换依赖

这是本章最核心的内容。很多血缘系统只画一种关系,结果就是「一条线走天下」,根本分不清是「表A的数据流到了表B」还是「表A的结构定义了表B的字段」。

我把它分成两种:

3.3.1 数据依赖(Data Dependency)

数据依赖,就是数据本身在流动。上游表的一条记录,经过 ETL 后变成了下游表的一条记录。这是最直观的血缘关系。

举个例子:

-- 上游表:orders
-- 下游表:daily_sales
INSERT INTO daily_sales
SELECT order_date, SUM(amount)
FROM orders
WHERE status = 'completed'
GROUP BY order_date;

这里 orders 和 daily_sales 之间就是数据依赖。如果你删了 orders 里的数据,daily_sales 的数据也会变。

实战技巧:数据依赖适合做「影响分析」。比如你想下线一张表,先查它的数据依赖下游,看看会影响哪些报表。

3.3.2 转换依赖(Transformation Dependency)

转换依赖,是逻辑或结构上的依赖。上游表不提供数据,而是提供「定义」——比如字段名、数据类型、业务规则。

我遇到过一个真实案例:

-- 上游表:user_profile(定义了用户等级字段)
-- 下游视图:vip_users
CREATE VIEW vip_users AS
SELECT user_id, user_name
FROM user_profile
WHERE user_level >= 5;

如果 user_profile 把 user_level 字段改名为 level,vip_users 视图就会报错。但注意,vip_users 并不直接存储 user_profile 的数据——它只是依赖了它的结构。

这就是转换依赖。它不关心数据值,只关心元数据。

对比维度 数据依赖 转换依赖
依赖对象 数据记录 元数据/结构
影响范围 数据值变化 Schema 变化
典型场景 ETL 管道、数据同步 视图、存储过程、接口定义
分析用途 数据溯源、影响分析 变更影响、兼容性检查

注意:很多血缘工具只采集数据依赖,忽略了转换依赖。结果就是「表结构改了,下游全挂了,血缘图上却一片平静」。我建议你在设计血缘模型时,两种关系都要采集。

3.4 核心知识体系:一张图看懂

下面这张图,把节点、边、关系类型串在了一起。你可以把它当作血缘分析的「最小知识集」。

数据血缘核心概念体系 血缘节点 • 数据源节点 (MySQL / Kafka / 文件) • 计算节点 (Spark / Flink / 存储过程) • 数据目标节点 (Hive / ClickHouse / 报表) 血缘边 • 源节点 ID • 目标节点 ID • 关系类型 • 时间戳 • 字段映射 • 过滤条件 关系类型 ① 数据依赖 数据值流动 ETL / 数据同步 ② 转换依赖 元数据/结构依赖 视图 / 存储过程 💡 核心原则 节点定义「谁」,边定义「怎么连」,关系类型定义「为什么连」。 三者缺一不可,否则血缘图只是一张「漂亮的蜘蛛网」。

3.5 实战:如何用代码表达血缘关系

理论说完了,来点实际的。我一般用 JSON 来表示一条完整的血缘关系:

{
  "edge_id": "edge_20240315_001",
  "source_node": {
    "id": "mysql://sales_db/orders",
    "type": "data_source",
    "name": "orders"
  },
  "target_node": {
    "id": "hive://dw/daily_sales",
    "type": "data_target",
    "name": "daily_sales"
  },
  "relation_type": "data_dependency",
  "timestamp": "2024-03-15T10:30:00Z",
  "field_mappings": [
    {"source": "order_date", "target": "sale_date"},
    {"source": "amount", "target": "total_amount"}
  ],
  "filter_condition": "status = 'completed'"
}

你看,这个 JSON 里把节点、边、关系类型、字段映射全包进去了。下游系统拿到这个 JSON,可以直接构建出血缘图,也能做字段级的影响分析。

我的习惯:把血缘关系存成两张表——edges 表存边的基本信息,edge_attributes 表存字段映射等扩展属性。这样查询快,扩展也灵活。

3.6 避坑指南

做血缘分析这几年,我踩过不少坑。挑三个最常见的分享给你:

  • 别把所有关系都画成数据依赖:视图、存储过程、接口定义这些,一定要用转换依赖。否则 Schema 变更时,你的血缘图根本预警不了。
  • 节点 ID 一定要全局唯一:别偷懒用表名。加上数据源前缀,比如 mysql://db/table 和 hive://db/table,避免混淆。
  • 别忘了时间维度:血缘关系会变。今天表A依赖表B,明天可能就改了。不加时间戳,你永远不知道哪条关系是「过期」的。

嗯,核心概念就这些。节点、边、关系类型——这三个东西搞清楚了,血缘分析的骨架就搭起来了。下一层,我们聊聊怎么把这些概念落地到流批一体的场景里。