3. 统一元数据模型设计:Common Information Model (CIM)

好,咱们进入第三章节。前面两章我们把多Catalog的痛点和整体架构聊清楚了。现在要动真格的了——怎么把这些乱七八糟的元数据统一起来?

答案就是:搞一个通用信息模型(CIM)。说白了,就是给所有元数据定一套通用的“普通话”。

我最早做这个的时候,心想不就是建个表嘛,有什么难的?结果被现实狠狠教育了一顿。不同数据源的元数据,结构差异大到离谱。Hive的表分区和MySQL的表分区,根本就不是一回事。你硬要往一个模子里套,最后只会得到一个四不像。

核心观点:CIM不是要消灭差异,而是要在差异之上建立统一的抽象层。就像交通规则,不关心你是轿车还是卡车,只关心你遵守红绿灯和车道线。

3.1 CIM设计三原则

我个人习惯,设计任何模型之前,先定几条铁律。CIM也不例外,我总结了三条:

  1. 最小化原则——只抽象所有数据源都有的共性。别贪心,别想着一次覆盖所有特性。覆盖80%的场景就够了,剩下的20%用扩展机制解决。
  2. 可扩展原则——模型必须能“长肉”。今天纳管Hive,明天纳管Kafka,后天纳管某个自研引擎。模型不能因为新数据源加入就推倒重来。
  3. 可追溯原则——每个元数据实体,必须能追溯到原始数据源。我遇到过最坑的事,就是元数据同步后,不知道这条记录是从哪儿来的,出了问题连锅都甩不出去。

我的经验:第一条“最小化原则”最容易被人忽略。很多团队一上来就想搞个大而全的模型,结果项目拖了半年,连最基本的表结构都没定下来。先做减法,再做加法。

3.2 核心实体关系建模

好,原则定好了,咱们看看CIM里到底有哪些核心实体。我把它分成三层:

  • 基础设施层:数据源(DataSource)、集群(Cluster)、连接信息(Connection)
  • 逻辑对象层:数据库(Database)、模式(Schema)、表(Table)、字段(Column)、分区(Partition)
  • 治理对象层:标签(Tag)、分级(Classification)、血缘(Lineage)、变更历史(ChangeLog)

你想想看,不管你是Hive、MySQL、MongoDB还是Elasticsearch,最终都能映射到这套模型上。举个例子:

原始数据源 DataSource类型 Database Table Column
Hive HIVE default orders order_id, amount
MySQL MYSQL shop_db users id, name, email
Elasticsearch ES log_index _doc timestamp, message

看到没?虽然底层千差万别,但在CIM里,它们都长一个样。这就是统一纳管的基础。

实体之间的关系也很清晰:一个DataSource下有多个Database,一个Database下有多个Table,一个Table下有多个Column。嗯,这里要注意,有些数据源(比如Elasticsearch)没有Database这个概念,我们就用默认值填充,或者干脆把Schema层当作Database用。

-- CIM核心实体关系(简化DDL示例)
CREATE TABLE cim_datasource (
    id          BIGINT PRIMARY KEY,
    name        VARCHAR(255) NOT NULL,
    ds_type     VARCHAR(50) NOT NULL,  -- HIVE, MYSQL, ES, KAFKA...
    connection_info TEXT,
    created_at  TIMESTAMP
);

CREATE TABLE cim_database (
    id          BIGINT PRIMARY KEY,
    ds_id       BIGINT REFERENCES cim_datasource(id),
    name        VARCHAR(255) NOT NULL,
    description TEXT
);

CREATE TABLE cim_table (
    id          BIGINT PRIMARY KEY,
    db_id       BIGINT REFERENCES cim_database(id),
    name        VARCHAR(255) NOT NULL,
    table_type  VARCHAR(50),  -- TABLE, VIEW, EXTERNAL...
    created_at  TIMESTAMP
);

CREATE TABLE cim_column (
    id          BIGINT PRIMARY KEY,
    table_id    BIGINT REFERENCES cim_table(id),
    name        VARCHAR(255) NOT NULL,
    data_type   VARCHAR(100),
    is_nullable BOOLEAN,
    comment     TEXT
);

避坑指南:我曾经在一个项目里,把DataSource的连接信息直接明文存到数据库里。结果安全审计的时候被骂得狗血淋头。连接信息一定要加密存储,或者用独立的密钥管理服务(KMS)来管理。

3.3 扩展属性机制

模型建好了,但现实世界永远比模型复杂。比如Hive表有STORED AS PARQUET属性,MySQL表有ENGINE=InnoDB属性,这些CIM核心模型里没有。怎么办?

答案就是扩展属性(Extended Attributes)。我把它设计成一个KV(键值对)结构,挂到任何实体上。

-- 扩展属性表
CREATE TABLE cim_ext_attr (
    id          BIGINT PRIMARY KEY,
    entity_type VARCHAR(50) NOT NULL,  -- TABLE, COLUMN, DATABASE...
    entity_id   BIGINT NOT NULL,
    attr_key    VARCHAR(255) NOT NULL,
    attr_value  TEXT,
    UNIQUE KEY uk_entity_attr (entity_type, entity_id, attr_key)
);

举个例子,Hive表的分区信息:

-- 给cim_table id=1001的表添加扩展属性
INSERT INTO cim_ext_attr (entity_type, entity_id, attr_key, attr_value)
VALUES ('TABLE', 1001, 'hive.partition.columns', 'dt,region');

INSERT INTO cim_ext_attr (entity_type, entity_id, attr_key, attr_value)
VALUES ('TABLE', 1001, 'hive.storage.format', 'PARQUET');

INSERT INTO cim_ext_attr (entity_type, entity_id, attr_key, attr_value)
VALUES ('TABLE', 1001, 'hive.location', '/user/hive/warehouse/orders');

这样做的好处很明显:

  • 核心模型保持稳定,不会因为新数据源加入而频繁改表结构
  • 扩展属性可以按需添加,不影响已有功能
  • 查询时,只需要JOIN一下扩展属性表,就能拿到完整信息

我的习惯:扩展属性的key命名,我建议用数据源类型.属性名的格式,比如hive.partition.columns、mysql.engine。这样一眼就能看出这个属性是哪个数据源特有的,避免命名冲突。

3.4 CIM整体结构图

说了这么多,咱们用一张图把CIM的整体结构串起来。下面这张SVG图,展示了核心实体、它们之间的关系,以及扩展属性如何挂载。

CIM统一元数据模型结构图 CIM统一元数据模型结构图 基础设施层 DataSource(数据源) 逻辑对象层 Database(数据库) Schema(模式) Table(表) Column(字段) Partition(分区) 治理对象层 Tag(标签) Classification(分级) Lineage(血缘) ChangeLog(变更) 扩展机制 Extended Attributes(扩展属性KV) 说明:实线箭头表示“一对多”的包含关系,虚线箭头表示治理对象可以挂载扩展属性。 扩展属性以KV形式挂载到任意实体上,保证模型的可扩展性。

3.5 避坑与最佳实践

最后,分享几个我在实战中踩过的坑,以及总结出来的最佳实践:

  • 不要用JSON字段代替扩展属性表——我见过有人把扩展属性直接塞到一个JSON字段里。查询时想过滤某个属性,性能直接崩了。扩展属性表一定要用独立的KV表,并且给entity_type + entity_id + attr_key建联合索引。
  • 实体ID要全局唯一——不同数据源的表,ID不能重复。我习惯用数据源类型 + 自增ID或者直接上UUID。否则后面做血缘分析时,ID冲突会让你怀疑人生。
  • 预留软删除字段——元数据同步过程中,经常会有表被删除。不要真删数据,加一个is_deleted字段,保留历史痕迹。我曾经因为真删了数据,导致某次数据回溯完全没法做。
  • 版本号控制——每次元数据变更,都带上一个版本号。这样你可以知道“这条元数据是什么时候变的”,对于排查问题非常有用。

再强调一次:CIM模型是统一纳管的基石。模型设计得不好,后面所有工作都是空中楼阁。花时间把模型讨论清楚,比急着写代码重要得多。