【Atlas】Atlas 中的 Relationship(关系)是如何建模的?

📅 2026/7/5 15:48:52 👁️ 阅读次数 📝 编程学习
【Atlas】Atlas 中的 Relationship(关系)是如何建模的?

Apache Atlas Relationship 建模机制深度解析:血缘与依赖关系的图谱基石

用户问题原文
18. Atlas 中的 Relationship(关系)是如何建模的?

本文将围绕上述问题,系统性剖析Apache Atlas 2.4.0Relationship(关系)的建模原理、存储机制、API 使用与生产落地路径。我们将从电商用户行为宽表治理的真实场景切入,深入源码层级解释 Relationship 如何作为元数据图谱的“神经突触”,支撑起跨引擎(Hive → Spark → ClickHouse)的端到端血缘追踪、影响分析与合规审计能力。全文基于Atlas 2.4.0 + Hadoop 3.3 + Hive 3.1 + Spark 3.3 + OpenJDK 11 + Ubuntu 20.04环境验证。


一、问题引入:为什么无法追溯“用户画像标签”来源?

某电商平台的数据团队构建了一张名为user_behavior_ck_table的 ClickHouse 宽表,用于实时推荐。该表由 Spark 作业从 Hive 表ods_user_event加工而来。然而,当安全团队要求审计“是否包含未成年人行为数据”时,数据地图无法展示该宽表的上游血缘——点击“查看血缘”仅显示孤立节点,无任何输入/输出连接。

经排查发现,根本原因在于:Spark Hook 上报的 Process Entity 未正确建立与输入表(ods_user_event)和输出表(user_behavior_ck_table)之间的 Relationship,导致 Atlas 图数据库中缺失关键边(Edge)。

这一故障暴露了 Relationship 在元数据治理中的核心地位——它不仅是血缘的“连线”,更是影响分析、变更传播、权限联动的“逻辑通道”

💡生活化类比
Relationship 就像城市交通路网中的“道路连接”——每个路口(Entity)本身有价值,但只有通过道路(Relationship)连接,才能形成可导航的路径(血缘)。如果地图只记录路口坐标而不记录道路,就无法规划从 A 到 B 的路线。
技术本质差异:道路是物理存在,而 Atlas Relationship 是逻辑声明,需显式建模并持久化到图数据库。


二、Relationship 官方定义与设计动机

2.1 官方定义(源自 Apache Atlas GitHub 源码)

在 Apache Atlas 官方文档 中,Relationship 被定义为:

“Relationships in Atlas represent the connections between entities. They are first-class citizens in the type system and are used to model complex metadata topologies such as lineage, ownership, and containment.”

更精确地说,Relationship 是一种独立于 Entity 的 Type 定义,用于描述两个或多个 Entity 之间的语义连接。它由以下核心要素构成:

  • RelationshipType:关系类型的声明(如hive_table_columns,dataset_process_dataset
  • EndDef:定义关系两端的实体类型、角色名与容器属性(isContainer
  • Cardinality:支持SINGLE,LIST,SET等多重性

所有 Relationship 均通过REST API/api/atlas/v2/types/relationshipdef管理,并作为JanusGraph 图数据库中的边(Edge)存储。

2.2 设计动机:为何不直接用 Entity 属性?

早期版本(< 1.0)曾尝试将关系作为 Entity 的属性(如hive_table.columns = [col1, col2]),但面临三大问题:

  1. 双向查询困难:无法高效查询“某列属于哪些表”
  2. 事务一致性差:更新表结构需同时修改表和列,易出现中间态
  3. 图遍历性能低:嵌套属性无法利用图数据库的邻接索引

因此,Atlas 2.0+ 引入独立 Relationship 模型,将关系提升为一等公民,实现:

  • 双向导航table → columnscolumn → table均 O(1)
  • 原子操作:增删列只需创建/删除 Relationship,无需修改表 Entity
  • 图优化:JanusGraph 可对 Relationship 建立边索引(Edge Index)

🔍源码证据
org.apache.atlas.model.instance.AtlasRelationship中,Relationship 被定义为独立对象:

publicclassAtlasRelationshipextendsAtlasStruct{privateStringguid;// 关系唯一IDprivateStringtypeName;// RelationshipType 名称privateAtlasObjectIdend1;// 一端实体引用privateAtlasObjectIdend2;// 另一端实体引用privatebooleanisCreatedByRelationship;// 是否由关系自动创建}

三、Relationship 核心模型与类型体系

3.1 RelationshipType 结构详解

一个完整的 RelationshipType 定义如下(以hive_table_columns为例):

{"name":"hive_table_columns","typeVersion":"1.0","endDef1":{"type":"hive_table","name":"columns","isContainer":true,"cardinality":"SET","isLegacyAttribute":false},"endDef2":{"type":"hive_column","name":"table","isContainer":false,"cardinality":"SINGLE","isLegacyAttribute":false}}

关键字段解释:

字段说明生产影响
endDef1.type/endDef2.type关系两端的 Entity Type必须已注册,否则创建失败
isContainer是否为“容器-成员”关系决定删除行为(见 3.3 节)
cardinality多重性(SINGLE/LIST/SET)影响 API 调用方式
name关系在 Entity 中的属性名UI/API 通过此名导航

3.2 三大 Relationship 类型

Atlas 2.4.0 内置三类核心 Relationship:

类型示例用途是否容器关系
Containment(包含)hive_table —[columns]→ hive_column描述“整体-部分”结构是(isContainer=true
Association(关联)spark_process —[inputs]→ hive_table描述血缘、依赖
Reference(引用)kafka_topic —[schema]→ avro_schema描述外部引用

⚠️关键区别
Containment 关系具有级联删除语义——删除hive_table会自动删除其所有hive_column(若atlas.graph.delete.container.entities=true,默认开启)。

3.3 容器关系(isContainer)的级联行为

isContainer=true时,Atlas 会自动处理级联操作:

  • 创建:先创建容器 Entity(如表),再创建成员 Entity(如列),最后建立 Relationship
  • 删除:删除容器 Entity 时,自动删除所有成员 Entity 及其 Relationship
  • 更新:替换成员列表时,自动计算增量(新增/删除 Relationship)

源码路径:org.apache.atlas.repository.store.graph.v2.EntityGraphMapperV2#mapRelationshipAttributes

// 简化逻辑:处理容器关系的级联删除if(relationshipDef.isContainer()){for(AtlasEntitymember:containerEntity.getRelatedEntities()){deleteEntity(member.getGuid());// 递归删除成员}}

💡生活化类比
isContainer=true就像“文件夹与文件”的关系——删除文件夹时,操作系统自动删除其中所有文件。
技术本质差异:文件系统删除是物理操作,而 Atlas 删除是逻辑标记(软删除),可通过hardDelete参数控制。


四、Relationship 在血缘建模中的实战应用

4.1 血缘三元组:inputs / outputs / process

Atlas 使用Process Entity + 两类 Association Relationship构建血缘:

  • process.inputs:指向输入数据集(如 Hive 表)
  • process.outputs:指向输出数据集(如 ClickHouse 表)

其 RelationshipType 定义如下(源自0010-DataSetTypes.json):

{"name":"dataset_process_dataset","endDef1":{"type":"Process","name":"inputs","isContainer":false,"cardinality":"SET"},"endDef2":{"type":"DataSet","name":"outputs","isContainer":false,"cardinality":"SET"}}

🔍注意:该 Relationship 是无向的,但通过endDef1.name="inputs"endDef2.name="outputs"赋予方向语义。

4.2 电商宽表血缘建模范例

我们以Spark 作业加工ods_user_eventuser_behavior_ck_table为例:

步骤 1:定义 Process Entity
{"entity":{"typeName":"spark_process","attributes":{"qualifiedName":"spark_job_user_behavior@prod-cluster","name":"UserBehaviorAggregation","owner":"data-team","startTime":1713900000000,"endTime":1713900600000}}}
步骤 2:建立 inputs Relationship
{"relationship":{"typeName":"dataset_process_dataset","end1":{"guid":"{{spark_process_guid}}","typeName":"spark_process"},"end2":{"guid":"{{ods_user_event_guid}}","typeName":"hive_table"}}}
步骤 3:建立 outputs Relationship

⚠️重要:由于dataset_process_dataset是无向关系,必须通过 API 指定哪一端是 input/output。Atlas 通过end1对应inputsend2对应outputs实现。

# 创建 inputs 关系curl-uadmin:admin-XPOST\-H"Content-Type: application/json"\-d'{ "relationship": { "typeName": "dataset_process_dataset", "end1": { "guid": "PROCESS_GUID" }, "end2": { "guid": "INPUT_TABLE_GUID" } } }'\http://localhost:21000/api/atlas/v2/relationship# 创建 outputs 关系(交换 end1/end2)curl-uadmin:admin-XPOST\-H"Content-Type: application/json"\-d'{ "relationship": { "typeName": "dataset_process_dataset", "end1": { "guid": "OUTPUT_TABLE_GUID" }, "end2": { "guid": "PROCESS_GUID" } } }'\http://localhost:21000/api/atlas/v2/relationship

验证点
查询血缘:

curl-uadmin:admin\"http://localhost:21000/api/atlas/v2/lineage/hive_table/inputs?depth=3&guid=OUTPUT_TABLE_GUID"

应返回ods_user_event表信息。

4.3 Mermaid 血缘关系图

渲染错误:Mermaid 渲染失败: Parse error on line 2: ...et_process_dataset\n(inputs)| B[spark_pr -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'

五、Relationship 存储与查询机制

5.1 JanusGraph 存储格式

Relationship 在 JanusGraph 中以边(Edge)形式存储,其属性包括:

JanusGraph Edge 属性Atlas 字段说明
~labelRelationshipType 名称hive_table_columns
end1_guidend1.guid一端实体 GUID
end2_guidend2.guid另一端实体 GUID
end1_typeend1.typeName一端类型
end2_typeend2.typeName另一端类型

🔍HBase RowKey 设计
JanusGraph 默认使用shard + vertexId + edgeId作为 RowKey,确保 Relationship 查询局部性。

5.2 高效查询:避免 N+1 问题

错误做法(N+1 查询):

// 先查表AtlasEntitytable=client.getEntityByAttribute("hive_table","qualifiedName","default.user_table");// 再循环查每列for(StringcolGuid:table.getRelationshipAttribute("columns")){AtlasEntitycol=client.getEntityByGuid(colGuid);// 每次 RPC 调用!}

正确做法(批量查询):

// 使用 AtlasClientV2 的批量接口List<String>colGuids=(List<String>)table.getAttribute("columns");List<AtlasEntity>columns=client.getEntitiesByGuids(colGuids);// 单次 RPC

⚠️性能陷阱
在 Web UI 中,若表有 1000 列,默认会发起 1001 次请求(1 次查表 + 1000 次查列)。生产环境需启用atlas.graph.storage.batch-loading=true优化


六、自定义 Relationship 开发实战

6.1 场景:IoT 设备指标与 Hudi 表关联

假设我们有IoT 设备iot_device_001,其指标写入Hudi 表iot_device_metrics_hudi。需建立device_produces_tableRelationship。

步骤 1:定义 RelationshipType
{"relationshipDefs":[{"name":"device_produces_table","typeVersion":"1.0","endDef1":{"type":"iot_device","name":"producedTables","isContainer":false,"cardinality":"SET"},"endDef2":{"type":"hudi_table","name":"producingDevice","isContainer":false,"cardinality":"SINGLE"}}]}
步骤 2:注册 Type
curl-uadmin:admin-XPOST\-H"Content-Type: application/json"\-d@iot_relationship.json\http://localhost:21000/api/atlas/v2/types/typedefs
步骤 3:创建 Relationship
// Java Client 示例AtlasRelationshiprelationship=newAtlasRelationship();relationship.setTypeName("device_produces_table");relationship.setEnd1(newAtlasObjectId(deviceGuid,"iot_device"));relationship.setEnd2(newAtlasObjectId(hudiTableGuid,"hudi_table"));AtlasClientV2client=newAtlasClientV2(...);client.createRelationship(relationship);

验证点
查询设备关联的表:

curl-uadmin:admin\"http://localhost:21000/api/atlas/v2/entity/guid/${deviceGuid}?excludeRelationshipAttributes=false"

响应中producedTables字段应包含 Hudi 表 GUID。


七、Relationship 生产调优与排障指南

7.1 常见故障与根因分析

故障现象根因诊断命令
血缘图显示“孤立节点”Relationship 未创建或类型错误GET /api/atlas/v2/relationship/guid/{relGuid}
删除表后列仍存在isContainer=true但配置atlas.graph.delete.container.entities=false检查application.properties
Relationship 创建慢JanusGraph 边索引未创建查看janusgraph.logIndex not found
双向导航失效RelationshipType 未正确定义endDef.nameGET /api/atlas/v2/types/relationshipdef/name/device_produces_table

7.2 性能调优参数

application.properties中调整:

# 启用 Relationship 批量加载(写入优化) atlas.graph.storage.batch-loading=true # Relationship 缓存大小 atlas.relationship.cache.size=10000 # 是否在创建 Entity 时自动创建 Relationship(默认 true) atlas.entity.relations.auto.create=true

7.3 监控指标(Prometheus)

指标说明
atlas_relationship_created_totalRelationship 创建总数
atlas_graph_edge_query_latency_msRelationship 查询延迟
janusgraph_edges_count图数据库边总数(反映血缘规模)

八、FAQ:高频问题解答

Q1:Relationship 与 Entity 属性有何性能差异?

维度RelationshipEntity 属性
存储JanusGraph 边(独立 Row)Entity JSON 内嵌
查询图遍历 O(1)需反序列化整个 Entity
更新原子操作需重写整个 Entity
双向导航原生支持需手动维护反向引用

结论超过 10 个关联对象时,必须使用 Relationship

Q2:如何修复断裂的血缘关系?

  1. 通过GET /api/atlas/v2/entity/guid/{guid}检查 Entity 的relationshipAttributes
  2. 若缺失,手动重建 Relationship
  3. 若 GUID 错误,需先修复上游 Entity 注册

⚠️Atlas 2.4.0 限制不支持 Relationship 的 PATCH 更新,只能删除重建。

Q3:Hive Hook 为何有时不创建 Relationship?

Hive Hook 仅在表结构变更(ALTER TABLE ADD COLUMN)时创建hive_table_columnsRelationship。若表已存在,首次上报不会补建。

解决方案
HiveMetaStoreBridge.registerTable()中强制重建关系:

// HiveMetaStoreBridge.javaif(tableExistsInAtlas){updateTableRelationships(table);// 补全列关系}

Q4:能否跨集群建立 Relationship?

可以,但需确保:

  • 两端 Entity 的qualifiedName包含正确集群名(如@clusterA,@clusterB
  • Atlas Server 能访问两个集群的元数据

示例hdfs://clusterA/path→ Spark →clickhouse://clusterB/table

Q5:Relationship 支持事务吗?

支持。Atlas 的EntityMutationResponse接口保证:

  • 同一批次的 Entity 和 Relationship 创建具有原子性
  • 失败时全部回滚

跨批次操作无事务保证,需业务层处理幂等。


九、总结与最佳实践

9.1 适用场景

  • 强血缘追踪:金融交易、医疗数据流
  • 复杂依赖管理:多级宽表、特征工程 pipeline
  • 动态影响分析:表结构变更影响评估

9.2 避坑指南

  • Always:使用isContainer=true建模“整体-部分”关系(如表-列)
  • Always:血缘 Relationship 使用内置dataset_process_dataset
  • Never:手动拼接 Relationship GUID(应通过 API 获取)
  • Never:在高并发场景关闭batch-loading

9.3 扩展方向

  • Relationship 版本化:记录关系变更历史
  • 动态 Relationship:基于规则引擎自动推导(如“同名字段即血缘”)
  • 跨 Atlas 实例同步:联邦 Relationship 管理

作者署名:九师兄

  • 专题目录:【Apache Atlas】Apache Atlas 资深工程师到专家实战之路目录
  • 总目录:【目录】技术体系目录

注意:本文由 AI 辅助生成,技术细节请以官方文档为准。生产环境使用前务必充分测试。