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 本章知识体系总览
下面这张图,把本章的核心逻辑串起来了。你可以把它当作「环境搭建的检查清单」。
这张图从左到右、从上到下,就是咱们搭建环境的完整流程。你照着这个顺序走,基本不会出大问题。
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,十分钟就能开始干活。这才是真正的「环境即代码」。