4. 数据质量规则引擎:规则定义语法、规则分类与执行引擎设计

聊到数据质量,很多人第一反应是「写脚本跑检查」。没错,脚本能干活,但项目一多你就知道痛了——每个脚本都是孤岛,规则散落在代码里,改个阈值得翻半天。我经历过一个数据仓库项目,光空值检查就写了三十多个 Python 脚本,后来想统一管理,差点没把自己逼疯。

所以,我们需要一个规则引擎。说白了,就是把「怎么检查数据」这件事,从代码里抽出来,变成可配置、可复用、可扩展的规则。这一章,我就带你把这个引擎的骨架搭起来。

4.1 规则定义语法:让业务人员也能写规则

规则引擎的第一步,是定义规则「长什么样」。我个人习惯用 JSON 或 YAML 来描述规则,结构清晰,解析也方便。下面是我常用的规则定义模板:

{
  "ruleId": "DQ-001",
  "ruleName": "用户邮箱格式校验",
  "ruleType": "FORMAT_CHECK",
  "target": {
    "datasource": "hive",
    "table": "ods_user_info",
    "column": "email"
  },
  "condition": {
    "operator": "regex_match",
    "value": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
  },
  "severity": "ERROR",
  "action": "BLOCK"
}

这里有几个关键字段:

  • ruleId:规则唯一标识,方便追踪和排错。
  • ruleType:规则分类,后面会细讲。
  • target:检查对象,数据源、表、字段。
  • condition:检查逻辑,包括操作符和期望值。
  • severity:严重级别,WARN / ERROR / FATAL。
  • action:触发后的动作,比如阻断、告警、跳过。
我的习惯:规则 ID 建议带前缀和序号,比如 DQ-001、DQ-002。别用 UUID,排查问题时你会感谢我的。

4.2 规则分类:四种核心检查类型

规则分类是引擎的骨架。我一般把规则分成四大类,每一类对应不同的检查场景。你想想看,数据质量问题翻来覆去就那几样——空值、格式、范围、业务逻辑。

4.2.1 空值检查

最基础,也最容易忽略。空值检查不只是判断 NULL,还要考虑空字符串、占位符(比如 'N/A'、'-')。

{
  "ruleType": "NULL_CHECK",
  "condition": {
    "operator": "is_null_or_empty",
    "value": null
  },
  "threshold": 0.05  // 允许 5% 的空值率
}

我在项目中遇到过,某个上游系统用 'NULL' 字符串表示空值,结果空值检查一直没触发。后来加了自定义空值列表才搞定。

4.2.2 格式校验

邮箱、手机号、身份证、日期……格式校验靠正则表达式。但要注意,正则不是万能的。

{
  "ruleType": "FORMAT_CHECK",
  "condition": {
    "operator": "regex_match",
    "value": "^1[3-9]\\d{9}$"
  }
}
避坑指南:我曾经用了一个过于宽松的手机号正则,结果把 '12345678901' 也放过去了。后来加了号段校验,才真正拦住脏数据。

4.2.3 范围校验

数值、日期、枚举值,都在范围内。范围校验支持闭区间、开区间、枚举列表。

{
  "ruleType": "RANGE_CHECK",
  "condition": {
    "operator": "between",
    "min": 0,
    "max": 150,
    "includeMin": true,
    "includeMax": false
  }
}

嗯,这里要注意:年龄范围校验,0 岁和 150 岁,边界值很容易踩坑。我建议 always 明确 includeMin/includeMax。

4.2.4 业务逻辑校验

这是最复杂的一类。业务逻辑校验往往涉及跨字段、跨表甚至跨系统的关联检查。

{
  "ruleType": "BIZ_LOGIC_CHECK",
  "condition": {
    "operator": "cross_field_compare",
    "fields": ["order_amount", "payment_amount"],
    "relation": "order_amount >= payment_amount"
  }
}

举个例子:订单金额必须大于等于支付金额。这个逻辑看起来简单,但实际数据里经常出现订单金额为 0 但支付金额为正的异常。为什么会这样?因为上游系统在退款场景下没处理好。

4.3 规则执行引擎设计:从配置到执行

规则定义好了,谁来跑?怎么跑?这就是执行引擎的事。我设计的引擎分三层:调度层、解析层、执行层。

4.3.1 引擎架构总览

先看一张图,把整体结构理清楚:

数据质量规则执行引擎架构图 数据质量规则执行引擎架构 调度层 定时触发 | 事件触发 | 手动触发 任务队列 | 优先级调度 | 依赖管理 解析层 规则解析器 | 语法校验 | 参数绑定 规则分类路由 | 执行计划生成 优化:规则合并、去重、剪枝 执行层 空值检查器 | 格式校验器 | 范围校验器 | 业务逻辑校验器 结果收集器 | 异常处理 | 重试机制 结果输出:通过/失败/告警/阻断

4.3.2 调度层:什么时候跑

调度层负责决定「什么时候执行」。我一般支持三种触发方式:

  • 定时触发:每天凌晨跑一次,适合离线数仓。
  • 事件触发:数据入库后自动触发,适合实时管道。
  • 手动触发:排查问题时用,或者上线前全量跑一遍。

调度层还要处理依赖关系。比如,表 A 的规则跑完,才能跑表 B 的规则。我曾经因为没处理好依赖,导致下游表用上游的脏数据跑了一整天,修复成本极高。

4.3.3 解析层:规则怎么理解

解析层是引擎的大脑。它把 JSON 规则翻译成可执行的检查计划。核心步骤:

  1. 语法校验:检查 JSON 结构是否完整,字段是否合法。
  2. 参数绑定:把规则里的占位符替换成实际值。
  3. 规则分类路由:根据 ruleType 把规则分发给对应的校验器。
  4. 执行计划生成:合并相同字段的规则,减少扫描次数。
优化技巧:解析层可以做规则剪枝。比如,某字段 100% 非空,空值检查可以直接跳过。我见过一个项目,剪枝后执行时间缩短了 40%。

4.3.4 执行层:规则怎么跑

执行层是干活的人。每个校验器只负责一种检查类型,职责单一。执行流程:

  1. 从解析层拿到执行计划。
  2. 连接数据源,读取目标数据。
  3. 逐条或批量执行检查。
  4. 收集结果,判断是否通过。
  5. 如果失败,根据 severity 执行对应动作。

执行层还要处理异常。比如,数据库连接超时、字段不存在、正则语法错误。我习惯加一个重试机制,最多重试 3 次,间隔 5 秒。如果还失败,就标记为「执行异常」,人工介入。

4.4 规则执行结果与反馈

规则跑完,结果怎么呈现?我一般用一张表来记录每次执行的详细信息:

字段 类型 说明
execution_id string 执行批次 ID
rule_id string 规则 ID
check_time timestamp 检查时间
total_rows int 检查总行数
failed_rows int 失败行数
status string PASS / FAIL / ERROR
detail json 失败样本数据

有了这张表,你可以做很多事:趋势分析、规则命中率统计、数据质量评分。我个人习惯每天跑完规则后,自动生成一份质量报告,发给数据负责人。

我的经验:detail 字段只保留前 10 条失败样本,避免存储爆炸。如果需要全量数据,可以单独落一份到 HDFS。

4.5 小结

这一章我们聊了规则引擎的三个核心部分:规则定义语法、规则分类、执行引擎设计。规则定义语法让规则变得可配置、可管理;规则分类帮你快速定位问题类型;执行引擎则把规则从配置变成行动。

你想想看,有了这套引擎,数据质量检查不再是散落的脚本,而是一个可扩展、可维护的系统。下次业务方说「帮我加个检查」,你只需要写一条 JSON 规则,而不是改代码、上线、测试。这才是数据治理该有的样子。