4. 数据目录架构设计:分层架构、存储选型、索引策略

数据目录这东西,说白了就是个“元数据的中枢神经系统”。你想想看,资产盘点要盘得清楚,首先得有个好架子把数据装起来。我这些年见过太多项目,一上来就堆功能,结果架构没想清楚,后面改得想哭。

今天咱们就聊聊,怎么搭一个能打的数据目录架构。我会把分层、存储、索引这三个核心点掰开揉碎了讲。

4.1 分层架构:别把所有逻辑塞一层

我个人习惯把数据目录分成三层:接入层、存储层、服务层。每层各司其职,互不干扰。

核心原则:层与层之间通过标准接口通信,下层不依赖上层。这样你换存储、改索引,上层业务代码几乎不用动。

数据目录分层架构 接入层(Ingestion Layer) API 网关 | 批量采集器 | 实时流订阅 | 手工录入 协议适配:JDBC / Kafka / HTTP / 文件扫描 存储层(Storage Layer) 元数据存储(关系型 / 图 / 文档) 全文索引(Elasticsearch) 缓存(Redis) | 对象存储(血缘文件、数据样本) 服务层(Service Layer) 搜索服务 | 血缘解析 | 资产分类 | 权限校验 对外暴露 RESTful API / GraphQL 💡 层间通信统一走内部 RPC 或消息队列,禁止跨层直接调用数据库

4.1.1 接入层:把数据“拽”进来

接入层负责跟各种数据源打交道。我建议你把它设计成插件化的。为什么?因为数据源类型太多了——Hive、MySQL、Kafka、HDFS、S3……你不可能一次性全写完。

我在项目中遇到过,客户突然说要接入一个自研的列式存储。幸好接入层是插件架构,我只需要写一个采集器适配器,注册进去就行。前后两天搞定。

小技巧:接入层最好加一个“限流熔断”机制。有一次某个数据源挂了,采集器疯狂重试,差点把目录服务拖垮。后来我加了基于令牌桶的限流,稳多了。

4.1.2 存储层:元数据的“家”

存储层是核心。这里要存三类东西:

  • 结构化元数据:表名、字段、类型、描述、标签——这些用关系型或图数据库。
  • 全文索引:支持模糊搜索、中文分词——Elasticsearch 是标配。
  • 附属数据:血缘关系图、数据样本、质量报告——这些体积大,放对象存储。

嗯,这里要注意:不要把血缘关系硬塞进关系型数据库。我见过有人把血缘存成 JSON 字段,查询时用 LIKE 匹配,那性能简直灾难。血缘天然适合图数据库。

4.1.3 服务层:对外提供能力

服务层封装了所有业务逻辑。搜索、分类、权限、血缘解析都在这一层。我个人习惯把服务层拆成原子服务和聚合服务。原子服务只做一件事,比如“根据 ID 查表元数据”;聚合服务编排多个原子服务,比如“搜索并返回关联的血缘链路”。

4.2 存储选型:没有银弹,只有权衡

存储选型是架构设计里最头疼的。我直接给结论:

存储类型 适用场景 我踩过的坑
PostgreSQL 核心元数据(表、字段、标签) 别把血缘存成 JSONB 嵌套查询,慢到怀疑人生
Neo4j / JanusGraph 血缘关系、数据链路 图数据库写入性能一般,批量导入时注意控制事务大小
Elasticsearch 全文搜索、中文分词、模糊匹配 索引 mapping 设计要提前想好,后期改 mapping 代价很大
Redis 热点缓存、高频查询 缓存穿透问题——空值也要缓存,不然流量直接打到 DB
对象存储(MinIO / S3) 数据样本、血缘 DAG 文件、质量报告 小文件太多会影响性能,建议合并成 Parquet 或 Avro

警告:千万不要用 MongoDB 存元数据。我曾经试过,看似灵活,但等你需要做跨文档关联查询时,就知道什么叫“灵活的反噬”。元数据的关系性很强,老老实实用关系型或图数据库。

4.3 索引策略:让搜索快如闪电

数据目录的核心体验就是搜索。用户可没耐心等 3 秒才出结果。我总结了一套索引策略,分为三层:

4.3.1 第一层:倒排索引(Elasticsearch)

这是主力。把所有元数据的文本字段(表名、字段名、描述、标签)都建倒排索引。注意几点:

  • 中文分词器:用 IK 分词器,支持细粒度切分。比如“用户登录表”能切出“用户”、“登录”、“表”。
  • 权重设计:表名权重最高,字段名次之,描述最低。这样搜索“订单”时,表名匹配的排前面。
  • 同义词扩展:比如“用户”和“customer”是同义词,搜索任一都能命中。
// ES 索引 mapping 示例(简化版)
{
  "mappings": {
    "properties": {
      "table_name": {
        "type": "text",
        "analyzer": "ik_max_word",
        "boost": 3.0
      },
      "column_name": {
        "type": "text",
        "analyzer": "ik_max_word",
        "boost": 2.0
      },
      "description": {
        "type": "text",
        "analyzer": "ik_smart",
        "boost": 1.0
      },
      "tags": {
        "type": "keyword"
      }
    }
  }
}

4.3.2 第二层:标签索引(关系型数据库)

标签是精确匹配场景。比如用户想找所有“PII 敏感”的表。这种场景用关系型数据库的 B-tree 索引就够了。我建议把标签设计成多值字段,用数组或关联表存储,然后建 GIN 索引(PostgreSQL 支持)。

避坑指南:我曾经把标签存成逗号分隔的字符串,然后用 LIKE 查询。结果数据量到 10 万条时,查询响应时间飙到 8 秒。后来改成数组 + GIN 索引,同样的查询降到 20 毫秒。

4.3.3 第三层:缓存索引(Redis)

热点数据一定要缓存。比如“最近 7 天被搜索最多的 100 个表”、“用户收藏的资产列表”。这些数据变化不频繁,但查询频率极高。用 Redis 的 Sorted Set 或 Hash 结构存,TTL 设置 5 分钟。

// Redis 缓存热点搜索词示例
// Key: hot_search:top100
// Value: ZSET (member = 搜索词, score = 搜索次数)
ZINCRBY hot_search:top100 1 "用户登录表"
ZREVRANGE hot_search:top100 0 9 WITHSCORES  // 获取 Top 10

4.4 架构落地的几个关键决策

最后聊几个我实际落地时遇到的决策点:

  1. 元数据变更怎么同步到 ES? 我建议用 CDC(Change Data Capture)监听数据库的变更日志,推送到消息队列,再由消费者更新 ES。不要用定时全量同步,太慢了。
  2. 血缘图要不要实时更新? 看场景。如果是离线数仓,每天凌晨批量更新一次就够了;如果是实时流,可以用异步任务,延迟控制在分钟级。
  3. 索引重建怎么做? 我习惯用“双索引”策略——建一个新索引,切换别名,再删旧索引。这样用户无感知。

总结一下:分层架构让你好维护,存储选型让你不踩坑,索引策略让你体验好。这三件事想清楚了,数据目录的架子就稳了。剩下的,就是往里面填内容了。