3. 环境搭建与基础配置:Docker部署、数据库初始化、元数据存储选型

说实话,做数据血缘这个方向,环境搭不好,后面全是坑。我见过太多团队,代码写得挺漂亮,结果一跑就挂,最后发现是数据库连不上,或者版本不兼容。所以这一章,咱们把地基打牢。

我会带着你从零开始,用 Docker 把整套环境拉起来。为什么选 Docker?说白了,就是为了「一次配置,到处运行」。你想想看,团队里有人用 Mac,有人用 Windows,还有人用 Linux,要是每个人手动装一遍 PostgreSQL、MySQL、Kafka……那画面太美我不敢看。

3.1 为什么我坚持用 Docker 做环境隔离

我记得刚入行那会儿,公司有个老项目依赖 MySQL 5.6,新项目要用 MySQL 8.0。两台服务装两个版本?太折腾。用 Docker 之后,一个容器跑 5.6,一个跑 8.0,互不干扰。数据血缘项目更复杂,要同时启动数据库、采集器、可视化服务,Docker Compose 一键搞定。

核心原则:每个服务一个容器,数据卷挂载到宿主机,配置通过环境变量注入。这样你删容器、重建容器,数据还在,配置也不会丢。

3.2 Docker Compose 编排:我的标准模板

下面这个 docker-compose.yml 是我在多个项目中打磨过的。它包含了元数据库(PostgreSQL)、缓存(Redis)、以及一个模拟的数据源(MySQL)。

version: '3.8'

services:
  # 元数据存储 - 主库
  postgres-metadata:
    image: postgres:15
    container_name: lineage-metadata-db
    environment:
      POSTGRES_USER: lineage_user
      POSTGRES_PASSWORD: lineage_pass_2024
      POSTGRES_DB: data_lineage
    ports:
      - "5432:5432"
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
      - ./init-scripts/postgres:/docker-entrypoint-initdb.d
    networks:
      - lineage-net
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U lineage_user -d data_lineage"]
      interval: 10s
      timeout: 5s
      retries: 5

  # 缓存 - 用于采集任务状态
  redis-cache:
    image: redis:7-alpine
    container_name: lineage-cache
    ports:
      - "6379:6379"
    volumes:
      - ./data/redis:/data
    networks:
      - lineage-net

  # 模拟数据源 - 用于测试采集
  mysql-source:
    image: mysql:8.0
    container_name: lineage-source-db
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: source_orders
      MYSQL_USER: app_user
      MYSQL_PASSWORD: app_pass
    ports:
      - "3306:3306"
    volumes:
      - ./data/mysql:/var/lib/mysql
      - ./init-scripts/mysql:/docker-entrypoint-initdb.d
    networks:
      - lineage-net

networks:
  lineage-net:
    driver: bridge

我的习惯:把初始化 SQL 脚本放在 ./init-scripts/ 目录下。容器第一次启动时会自动执行,建表、建索引、插测试数据一步到位。后面你重置环境,删掉 ./data/ 目录再重启就行。

3.3 数据库初始化:别小看这几张表

元数据存储是数据血缘的「大脑」。所有采集到的表结构、字段映射、ETL 依赖关系,都得存下来。我见过有人直接用业务库当元数据库,结果业务一改表结构,血缘就乱了。

下面是我常用的初始化脚本,针对 PostgreSQL。注意看表结构的设计思路。

-- 01_create_metadata_tables.sql
-- 数据源注册表
CREATE TABLE IF NOT EXISTS data_sources (
    id SERIAL PRIMARY KEY,
    source_name VARCHAR(128) NOT NULL UNIQUE,
    source_type VARCHAR(32) NOT NULL,  -- 'mysql', 'postgresql', 'hive', 'kafka'
    connection_config JSONB NOT NULL,
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 表元数据
CREATE TABLE IF NOT EXISTS table_metadata (
    id SERIAL PRIMARY KEY,
    source_id INTEGER REFERENCES data_sources(id),
    schema_name VARCHAR(128) NOT NULL,
    table_name VARCHAR(128) NOT NULL,
    table_type VARCHAR(32) DEFAULT 'TABLE',  -- 'TABLE', 'VIEW', 'CTE'
    columns_count INTEGER,
    row_count_estimate BIGINT,
    last_analyzed_at TIMESTAMP,
    UNIQUE(source_id, schema_name, table_name)
);

-- 字段血缘关系
CREATE TABLE IF NOT EXISTS column_lineage (
    id SERIAL PRIMARY KEY,
    source_table_id INTEGER REFERENCES table_metadata(id),
    source_column VARCHAR(128) NOT NULL,
    target_table_id INTEGER REFERENCES table_metadata(id),
    target_column VARCHAR(128) NOT NULL,
    transform_expression TEXT,  -- 转换逻辑,如 'UPPER(name)'
    lineage_type VARCHAR(16) DEFAULT 'direct',  -- 'direct', 'transform', 'aggregate'
    confidence DECIMAL(3,2) DEFAULT 1.00,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 索引:血缘查询的核心
CREATE INDEX idx_lineage_target ON column_lineage(target_table_id, target_column);
CREATE INDEX idx_lineage_source ON column_lineage(source_table_id, source_column);

我曾经踩过的坑:一开始没给 column_lineage 表加索引,结果采集了 10 万条血缘关系后,查询一张表的上下游要等 30 秒。后来加了联合索引,查询降到 50 毫秒。所以,索引一定要在建表时就规划好,别等数据量大了再补。

3.4 元数据存储选型:PostgreSQL vs MySQL

这个问题几乎每个学员都会问。我直接给结论:数据血缘项目,首选 PostgreSQL。为什么?看下面的对比。

对比维度 PostgreSQL MySQL
JSON 支持 原生 JSONB,支持索引和内部查询 JSON 类型,功能较弱
递归 CTE 支持,适合血缘上下游递归查询 8.0 开始支持,性能一般
数组类型 原生支持,存储字段列表很方便 不支持,需用关联表
扩展生态 pg_stat_statements, postgis 等 插件较少
并发写入 MVCC 成熟,读写不互斥 InnoDB 也不错,但大并发略逊
学习成本 稍高,但值得投入 入门简单,但深度不够

你可能会问:「那 MySQL 完全不能用吗?」也不是。如果你的团队对 MySQL 特别熟,而且血缘数据量不大(百万级以内),MySQL 8.0 也能跑。但一旦涉及到递归查询上下游,PostgreSQL 的 WITH RECURSIVE 写起来是真的爽。

我的建议:新项目直接上 PostgreSQL 15+。如果你公司已有 MySQL 基础设施,可以用 PostgreSQL 做元数据库,MySQL 只做数据源。这样两边的优势都能用上。

3.5 验证环境:一条 SQL 搞定

环境搭好之后,怎么确认一切正常?我习惯跑一条递归查询,看看 PostgreSQL 能不能正确返回血缘链路。

-- 验证:查询某个字段的完整血缘链路
WITH RECURSIVE lineage_tree AS (
    -- 起点:找到目标字段
    SELECT 
        cl.id,
        cl.source_table_id,
        cl.source_column,
        cl.target_table_id,
        cl.target_column,
        1 AS depth,
        ARRAY[cl.id] AS path
    FROM column_lineage cl
    WHERE cl.target_column = 'order_amount'
      AND cl.target_table_id = 42

    UNION ALL

    -- 递归:向上游追溯
    SELECT 
        cl.id,
        cl.source_table_id,
        cl.source_column,
        cl.target_table_id,
        cl.target_column,
        lt.depth + 1,
        lt.path || cl.id
    FROM column_lineage cl
    INNER JOIN lineage_tree lt
        ON cl.target_table_id = lt.source_table_id
        AND cl.target_column = lt.source_column
    WHERE NOT cl.id = ANY(lt.path)  -- 防止循环
)
SELECT * FROM lineage_tree ORDER BY depth DESC;

如果这条 SQL 能跑出结果,说明你的 PostgreSQL 安装正确,表结构也建好了。如果报错,多半是索引没建或者权限不对。检查一下 docker logs lineage-metadata-db,错误信息一般都很清楚。

3.6 本章知识体系总览

下面这张图,把本章的核心逻辑串起来了。你可以把它当作「环境搭建的检查清单」。

环境搭建与基础配置 · 知识体系 Docker Compose 编排 PostgreSQL 元数据库 Redis 缓存 MySQL 模拟数据源 数据库初始化:建表、索引、测试数据 选型决策:PostgreSQL(主推) vs MySQL(备选) 验证:递归 CTL 查询血缘链路 💡 核心原则 容器隔离 · 数据持久化 配置环境变量注入

这张图从左到右、从上到下,就是咱们搭建环境的完整流程。你照着这个顺序走,基本不会出大问题。

3.7 几个让你少熬夜的 Tips

  • 端口冲突:启动前先 lsof -i :5432 看看端口有没有被占。我遇到过 Jenkins 占用了 5432,折腾了半小时才发现。
  • 数据卷权限:Linux 上挂载数据卷时,注意 PostgreSQL 容器内的用户是 uid=999。如果宿主机目录权限不对,容器会启动失败。加个 chown 999:999 ./data/postgres 就行。
  • 初始化脚本执行顺序:文件名按数字前缀排序,比如 01_xxx.sql、02_xxx.sql。PostgreSQL 的 docker-entrypoint-initdb.d 是按字母顺序执行的。
  • 网络问题:如果容器之间连不上,检查是不是都在同一个 lineage-net 网络里。跨网络通信要额外配置。

一个小技巧:把 docker-compose.yml 和初始化脚本放到 Git 仓库里。新同事入职,git clone 下来,docker-compose up -d,十分钟就能开始干活。这才是真正的「环境即代码」。