DataHub 通用元数据服务(GMS)深度解析:Rest.li API、GMA 存储与元数据服务架构
【免费下载链接】datahubThe Context Platform for your Data and AI Stack项目地址: https://gitcode.com/GitHub_Trending/da/datahub
本文基于仓库 docs/what/gms.md 展开。GMS(Generalized Metadata Service)是 DataHub 中承载元数据读写能力的微服务层,它对外暴露 Rest.li API,对内通过 GMA DAO 访问底层元数据存储。读完本文,你将理解 GMS 在 DataHub 整体架构中的位置、它与 GMA(Generalized Metadata Architecture)及元数据图(Metadata Graph)的关系、其事件驱动的数据流转链路(MCP → MCL → 索引/图更新),并能结合仓库源码定位到对应的服务实现与配置文件。
什么是 GMS?
GMS 全称Generalized Metadata Service(通用元数据服务)。在 DataHub 的元数据架构中,凡是 onboarded 到 GMA 的 实体(entities) 元数据,都由一类名为 GMS 的微服务对外提供访问。
GMS 具有两个关键特征:
- 对外提供 Rest.li API:Rest.li 是 LinkedIn 开源的 RESTful 框架(rest.li),GMS 以 Rest.li 资源(Resource)的形式暴露元数据的读写接口,例如实体 CRUD、Aspect 查询、关系查询等。
- 通过 GMA DAO 访问元数据:GMS 自身不直接操作数据库,而是依赖 GMA DAO 这一数据访问抽象层来读写底层存储。
从源码看,GMS 的 Rest.li 资源实现集中在 metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/ 目录下,例如:
- EntityResource.java:实体(Entity)的 CRUD 与批量操作;
- EntityV2Resource.java:实体 V2 接口(支持 Aspect 级读写);
- EntityVersionedV2Resource.java:带版本信息的实体接口;
- AspectResource.java:单个 Aspect 的读写;
- Relationships.java:关系/血缘查询;
- UsageStats.java 与 Analytics.java:使用统计与分析。
这些资源类统一由 Spring 装配进 GMS 的 Web 容器(war 模块见 metadata-service/war),共同构成对外可访问的 Rest.li API 面。
GMS 与 GMA:分布式设计下的元数据服务
要理解 GMS,必须先理解其背后的架构理念——GMA(Generalized Metadata Architecture),见 docs/what/gma.md。
GMA 是 DataHub 的后端基础设施,它的核心主张是:不试图用单一存储满足所有查询,而是让多种专用存储各司其职,以高效支撑元数据领域最常见的四类查询模式:
| 查询模式 | 语义 | 对应存储 |
|---|---|---|
| Document-oriented CRUD | 以主键(如dataset-urn)为中心的文档读写 | 文档存储(MySQL / Postgres / Cassandra 等 RDBMS) |
| Complex queries | 复杂查询,包括跨分布式表的关联 | 关系型/文档存储 + 搜索索引 |
| Graph traversal | 图遍历(血缘、上下游、成员关系) | 图索引(Neo4j 等图数据库,详见 graph.md) |
| Fulltext search and autocomplete | 全文检索与自动补全 | 搜索索引(Elasticsearch,详见 search-index.md) |
GMA 同时拥抱分布式模型:每个团队可以拥有、开发并运营自己的元数据服务(也就是自己的 GMS 实例),而元数据会被自动聚合,填充到中央的 元数据图(metadata graph) 与 搜索索引(search index) 中。这套机制之所以可行,是因为 GMA标准化了元数据模型与访问层——模型统一用 PDL 定义,访问统一走 DAO 抽象。
而 GMS 文档所描述的正是这一理念下的落地形态:
GMA 被设计为支持一个分布式的 GMS 集群(fleet),每个 GMS 服务于 GMA 图中的一部分实体。不过,为了简化部署,DataHub 当前包含一个集中的、单一的 GMS来服务所有实体。
也就是说,虽然架构上支持"多 GMS 各管一摊",但开箱即用的 DataHub 默认以单 GMS 部署方式提供完整功能。这条设计取舍是理解 GMS 定位的关键:它是"服务实体子集的通用模式",也是"开箱即用的单例实现"。
GMS 背后的元数据模型:实体、Aspect、关系
GMS 对外读写的最小单元不是"实体"这个整体,而是Aspect(元数据切面)。理解这一层模型,才能看懂 GMS 的 API 设计与事件流转。
- 实体(Entity):一个被建模的元数据类型,如 Dataset、Dashboard、CorpUser、Group 等,每个实体有唯一的 URN 标识。
- Aspect(元数据切面):见 docs/what/aspect.md,一个 Aspect 是 PDL 定义的
record,代表某一类特定元数据,例如Ownership(所有权)、SchemaMetadata(表结构)、UpstreamLineage(血缘上游)。Aspect 本身没有意义,必须挂载在某个实体上("谁的 ownership?")。Aspect 默认不可变(immutable),每次更新都产生新版本(可配置保留策略,例如只保留最近 X 个版本或最近 30 天的变更)。 - 关系(Relationship):见 docs/what/relationship.md,关系是两个实体之间的具名有向关联,例如
Group到User的HasMember。关系中比较实用的建模方式是"外键式"——在 Aspect 中以 URN 数组保存关联实体,再通过@Relationship注解显式声明关系类型与目标实体类型,图索引会据此生成边。
GMS 的 Aspect 级 API(如EntityV2Resource、AspectResource)正是围绕"实体 + Aspect"的模型设计的:客户端提交对某实体某 Aspect 的修改请求,GMS 校验并落库,再对外广播变更事件。
GMS 的读写链路:MCP 提案与 MCL 日志
GMS 的写入与读取不是孤立的,它处于一条完整的事件驱动链路中。这条链路由 docs/what/mxe.md 详细定义,核心事件如下:
- Metadata Change Proposal(MCP):一个"变更提案",表示请求修改某实体的某个 Aspect。MCP 可以由低层摄取 API 的客户端(如各类 ingestion source)发出,DataHub Python API 也提供发送 MCP 的接口。默认 Kafka topic 为
MetadataChangeProposal_v1。GMS 的存储层监听 MCP,尝试将其应用到元数据图上。 - Metadata Change Log(MCL):一个"已提交变更日志",在变更成功写入持久化存储后立即发到 Kafka。MCL 分两种:
- Versioned(版本化):记录 Aspect 的"最新状态",如所有权、文档,默认 topic 为
MetadataChangeLog_Versioned_v1; - Timeseries(时间序列):记录某一时刻发生的事件型元数据,如数据画像(profiling),默认 topic 为
MetadataChangeLog_Timeseries_v1。
- Versioned(版本化):记录 Aspect 的"最新状态",如所有权、文档,默认 topic 为
- Platform Event(PE):DataHub 自身产生的业务事件,例如实体变更事件(Entity Change Event),是 Actions 框架的重要输入,默认 topic 为
PlatformEvent_v1。
一次典型的元数据写入在 GMS 侧的流转是:客户端发送 MCP → GMS 存储层校验并写入文档存储 → 提交成功后发出 MCL(versioned 或 timeseries)→ 下游消费者据此更新图索引与搜索索引。
值得注意的是,并非每个 MCP 都会产生 MCL:GMS 服务层会忽略对元数据的重复变更(即幂等去重),这一点在 docs/architecture/metadata-serving.md 中有明确说明。
Serving 层组件:GMS 如何协同工作
在 Serving 架构(docs/architecture/metadata-serving.md)中,围绕 GMS 有以下核心组件协同工作:
| 组件 | 职责 | 仓库位置 |
|---|---|---|
| Metadata Storage | 将元数据持久化到文档存储(MySQL / Postgres / Cassandra 等 RDBMS) | 见 metadata-io |
| Metadata Change Log Stream(MCL) | 变更提交后经 Kafka 广播 MCL,是公开 API,外部系统(如 Actions 框架)可订阅并实时响应 | 见 docs/what/mxe.md |
| Metadata Index Applier(mae-consumer-job) | 消费 MCL,将变更应用到图索引与搜索索引 | metadata-jobs/mae-consumer-job |
| Metadata Query Serving | 按查询类型路由到对应存储:主键读走文档存储,二级索引/全文/高级搜索走搜索索引,血缘等复杂图查询走图索引 | 见下节 |
其中mae-consumer-job是一个实体无关(entity-agnostic)的 Spring 作业:它根据变更的 Aspect 类型,调用对应的图/搜索索引 Builder 来更新索引。为了保证处理顺序正确,MCL 按实体 URN 进行 key 分区——同一实体的所有变更会被单个线程顺序处理,从而避免乱序导致的索引不一致。
查询路由:GMS 面对四类读请求的路径
GMS 在提供查询能力时,会根据查询类型将请求路由到不同的后端(见 metadata-serving.md):
- 主键读取(例如根据
dataset-urn获取表结构元数据)→ 路由到文档存储; - 二级索引读取→ 路由到搜索索引(也可使用强一致的二级索引支持);
- 全文与高级搜索→ 路由到搜索索引;
- 复杂图查询(如血缘)→ 路由到图索引。
这套路由背后的抽象是DAO(Data Access Object)体系,包括文档存储 DAO、Search DAO 与 Graph DAO。GMS 只依赖 DAO 接口,而不关心底层具体是哪种数据库实现,这正是"标准化访问层"的直接体现,也是 GMA 文档中"distributed model 下各团队可自由选择存储实现"能成立的原因。
从源码验证 GMS 的实现
在仓库中可以找到 GMS 落地实现的关键证据:
- Rest.li 资源层:metadata-service/restli-servlet-impl 下以
@RestLiCollection/@RestLiSimpleResource注解声明了一批资源类(EntityResource、EntityV2Resource、AspectResource、Relationships、UsageStats、Analytics等),它们就是"GMS 对外提供 Rest.li API"这句话的源码对应。 - 服务装配层:metadata-service/factories 与 metadata-service/war 负责把这些资源与 DAO、存储、Kafka 生产者/消费者装配成可运行的 GMS 应用。
- 索引应用作业:metadata-jobs/mae-consumer-job 实现了"消费 MCL → 更新图与搜索索引"的链路,对应上文 Metadata Index Applier 组件。
- DAO 抽象:GMA DAO 的说明见 metadata-serving.md,它同时支持本地 DAO 与面向远程 GMS 的 Remote DAO——后者意味着客户端进程可以通过 Remote DAO 把读写请求转发到运行中的 GMS,这也是多 GMS 分布式部署模式下客户端接入的方式。
小结
GMS 是 DataHub 元数据服务的"门面":对内它封装了 GMA 多存储架构的复杂度,对外通过标准化的 Rest.li API 提供实体/Aspect 的读写与查询;事件机制(MCP/MCL)让它既能接收外部摄取的数据,又能把变更实时广播给索引应用作业与 Actions 框架。理解 GMS,就等于理解了 DataHub 元数据平面的主干。
想要继续深入,可以按以下路径阅读仓库文档:
- 元数据模型:metadata-model.md、extending-the-metadata-model.md
- 底层架构:gma.md、graph.md、search-index.md、urn.md
- 事件体系:mxe.md
- Serving 架构:metadata-serving.md
- 元数据摄取:metadata-ingestion.md
【免费下载链接】datahubThe Context Platform for your Data and AI Stack项目地址: https://gitcode.com/GitHub_Trending/da/datahub
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考