4、数据质量元数据管理:数据源注册、表结构采集、字段级别元数据、血缘关系
聊到数据质量监控,很多人第一反应就是写规则、跑校验。但说实话,如果底层的元数据都没管好,那些规则就像建在沙滩上的城堡。我见过太多团队,规则写得飞起,结果数据源换了连接串都不知道,表结构改了字段类型也不清楚——那监控出来的结果,你敢信吗?
所以这一章,咱们就扎扎实实地把元数据管理这块地基打牢。说白了,就是搞清楚四个问题:数据从哪来?表长什么样?字段有哪些讲究?数据是怎么流转的?
4.1 数据源注册:给数据上个户口
数据源注册,听起来挺简单,不就是配个连接信息嘛。但我在项目中踩过坑——有一次,生产环境的MySQL连接池爆了,排查了半天,发现是某个同事把测试库的连接配到了线上任务里。从那以后,我坚持所有数据源必须走注册流程,不能直接写死在代码里。
一个完整的数据源注册,至少包含这些信息:
| 字段 | 说明 | 示例 |
|---|---|---|
| 数据源名称 | 唯一标识,建议按业务域命名 | order_mysql_prod |
| 数据源类型 | MySQL、PostgreSQL、Hive、Kafka等 | MySQL |
| 连接信息 | 主机、端口、库名、用户名、密码 | 192.168.1.100:3306/orders |
| 环境标识 | dev/test/prod | prod |
| 负责人 | 谁负责维护这个数据源 | 张三 |
| 注册时间 | 自动生成 | 2024-01-15 10:00:00 |
代码实现上,我一般用工厂模式来处理不同类型的数据源:
// 数据源注册接口
public interface DataSourceRegistrar {
boolean register(DataSourceConfig config);
boolean testConnection(DataSourceConfig config);
}
// MySQL实现
public class MySQLRegistrar implements DataSourceRegistrar {
@Override
public boolean register(DataSourceConfig config) {
// 1. 校验参数完整性
// 2. 测试连通性
// 3. 写入元数据中心
// 4. 返回注册结果
return true;
}
}
4.2 表结构采集:把元数据抓上来
数据源注册好了,下一步就是采集表结构。你想想看,如果连表里有哪些字段、字段类型是什么都不知道,你怎么写质量规则?
表结构采集,我把它分成两个层面:
- 全量采集:每天凌晨跑一次,把整个库的表结构拉下来。适合表结构变化不频繁的场景。
- 增量采集:监听DDL变更,比如ALTER TABLE,实时更新元数据。适合表结构经常变动的场景。
嗯,这里要注意一个坑。我曾经遇到过,某个业务表一天之内改了三次字段类型,从int改成varchar又改回int。如果只靠全量采集,中间那段时间的质量监控全是错的。所以我现在倾向于「全量+增量」双保险。
采集到的表结构元数据,我一般存成这样的结构:
{
"tableId": "tbl_001",
"tableName": "order_info",
"database": "orders_db",
"columns": [
{
"columnName": "order_id",
"dataType": "bigint",
"isNullable": false,
"defaultValue": null,
"comment": "订单ID"
},
{
"columnName": "order_amount",
"dataType": "decimal(10,2)",
"isNullable": true,
"defaultValue": "0.00",
"comment": "订单金额"
}
],
"lastUpdated": "2024-01-15 12:00:00"
}
4.3 字段级别元数据:细节里的魔鬼
字段级别的元数据,说白了就是给每个字段做「画像」。我刚开始做数据质量时,只关注表级别的信息,后来发现根本不够用。举个例子,一个字段叫status,你光知道它是int类型有什么用?你得知道它的枚举值范围、业务含义、甚至它的数据分布情况。
字段级别元数据,我建议至少包含这些:
- 基础信息:字段名、类型、长度、是否为空、默认值
- 业务信息:字段中文名、业务含义、所属业务域
- 约束信息:主键、外键、唯一约束、检查约束
- 质量信息:空值率、唯一率、枚举值分布、最大值、最小值
- 敏感信息:是否包含PII(个人身份信息)、是否需要脱敏
phone,类型是varchar(11)。所有人都以为它是手机号,结果后来发现里面存的是座机号,还有几个是「无」字。如果当时字段级别元数据里标注了「手机号」的约束,这种问题早就能发现了。
字段级别的元数据采集,我一般用JDBC的DatabaseMetaData来获取:
DatabaseMetaData metaData = connection.getMetaData();
ResultSet columns = metaData.getColumns(null, null, tableName, null);
while (columns.next()) {
String columnName = columns.getString("COLUMN_NAME");
int dataType = columns.getInt("DATA_TYPE");
String typeName = columns.getString("TYPE_NAME");
int columnSize = columns.getInt("COLUMN_SIZE");
boolean nullable = columns.getInt("NULLABLE") == 1;
// ... 其他字段
}
4.4 血缘关系:数据从哪里来,到哪里去
血缘关系,是元数据管理里最难啃的骨头,但也是最有价值的。说白了,就是搞清楚数据是怎么流转的——从源表到中间表,再到最终的报表或API。
为什么要做血缘?我举个例子。某天业务反馈说「昨天的销售额报表数据不对」。如果没有血缘关系,你得从报表一层层往回查,可能查半天才发现是某个ETL脚本里join条件写错了。但如果有血缘关系,你一眼就能看到这条数据链路,定位问题快得多。
血缘关系的采集,我分成三种方式:
- 静态解析:解析SQL语句,分析SELECT、FROM、JOIN、INSERT等关键字,提取表与表之间的关系。适合离线场景。
- 动态追踪:在ETL执行过程中,通过拦截器或代理,实时记录数据的输入输出。适合实时场景。
- 人工标注:对于一些复杂的、无法自动解析的场景,由数据工程师手动标注血缘关系。
下面这张图,是我常用的血缘关系存储模型:
血缘关系的存储,我一般用图数据库,比如Neo4j。因为血缘本质上是个有向无环图(DAG),用图数据库查询起来特别方便。比如你想查某个字段的所有上游依赖,一条Cypher语句就搞定了:
MATCH (f:Field {name: 'sales_amount'})<-[:PRODUCES*]-(upstream)
RETURN upstream
好了,这一章的内容就这些。元数据管理看起来琐碎,但它是数据质量监控的基石。数据源注册、表结构采集、字段级别元数据、血缘关系,这四个环节环环相扣,缺一不可。把这块做好了,后面的质量规则才能有的放矢。
公众号:蓝海资料掘金营,微信deep3321