3、数据契约生命周期:设计、发布、消费、监控、退役

数据契约不是写一份就完事的文档。它有自己的生命轨迹。

我见过太多团队把契约当成「上线前的一次性约定」。签完就扔进 wiki 吃灰,三个月后数据对不上,谁都不认账。说白了,契约必须像软件一样管理——有设计、有版本、有监控、有退役。

这一章,我把整个生命周期拆成五个阶段。每个阶段都有坑,也有我踩过之后的解法。

核心观点: 数据契约的生命周期 = 微服务的 API 管理流程。设计时评审,发布时注册,消费时校验,运行时监控,废弃时通知。少一步,契约就变成废纸。
数据契约生命周期五阶段 ① 设计 Schema + 约束 ② 发布 注册 + 版本化 ③ 消费 校验 + 绑定 ④ 监控 SLA + 告警 ⑤ 退役 下线通知 持续反馈闭环 关键原则: • 每个阶段必须有明确的「准入」和「准出」标准 • 阶段之间通过契约仓库(Schema Registry)传递状态 • 任何阶段发现问题,都要回退到设计阶段重新评审

3.1 设计阶段:定结构、定约束、定语义

设计阶段决定了契约的质量上限。我个人习惯,设计时至少回答三个问题:

  • 数据结构是什么? 字段名、类型、是否必填、默认值。
  • 业务约束有哪些? 唯一性、取值范围、枚举列表、外键依赖。
  • 语义承诺是什么? 这个字段代表什么?是下单时间还是支付时间?

我在项目中遇到过最典型的坑:字段名叫 status,生产者认为是「订单状态」,消费者认为是「支付状态」。两边都没错,但数据对不上。后来我们强制在契约里加上了 description 和 example 字段。

我的习惯: 设计阶段就引入 Protobuf 或 Avro 的 IDL,不要用 JSON Schema 凑合。IDL 能表达嵌套结构、枚举、默认值,还能直接生成代码。JSON Schema 做校验还行,做契约定义太弱。

设计阶段的产出物,我建议包含以下内容:

产出物 说明 示例
Schema 定义 字段名、类型、层级结构 order_id: string (required)
约束清单 唯一键、外键、取值范围 status IN ('pending','paid','cancelled')
语义字典 每个字段的业务含义 create_time = 用户下单时刻
SLA 草案 数据延迟、可用性、完整性目标 延迟 < 5min, 可用性 99.9%

3.2 发布阶段:注册、版本化、通知

设计好的契约不能躺在本地。必须发布到一个中心化的契约仓库(Schema Registry)。

为什么?你想想看,如果每个团队各自维护一份契约副本,改了一个字段,其他团队根本不知道。我见过最夸张的情况——生产者和消费者各有一份 JSON,字段名都不一样,数据管道跑了半年没人发现。

发布阶段的核心动作:

  1. 注册: 将契约推送到 Schema Registry,生成唯一 ID。
  2. 版本化: 每次修改必须升版本。兼容性检查(向后兼容、向前兼容、完全兼容)由 Registry 自动执行。
  3. 通知: 通过 webhook 或消息队列,通知所有订阅方「契约有更新」。
我曾经踩过的坑: 有一次我们升级了订单表的字段类型,从 int32 改成 int64。Registry 检查说「向后兼容」,就自动发布了。结果下游的 Flink 作业用的旧版 Avro 类,反序列化直接崩溃。后来我加了一条规则:涉及类型变更的版本,必须标记为 BREAKING,人工审批后才能发布。

发布后的契约,应该处于以下三种状态之一:

状态 含义 消费者能否使用
DRAFT 设计阶段,还在评审 否
ACTIVE 已发布,可消费 是
DEPRECATED 即将退役,建议迁移 是(但会告警)

3.3 消费阶段:绑定、校验、降级

消费者拿到契约后,不是直接「拿来用」就完了。消费阶段要做三件事:

  • 绑定: 消费者声明「我消费的是契约 v1.2.3」。这个绑定关系要注册回 Registry,方便后续变更影响分析。
  • 校验: 每次收到数据,用契约 Schema 做实时校验。字段缺失、类型不对、枚举值越界,立刻拒绝或降级。
  • 降级: 如果数据不符合契约,消费者要有兜底策略。是用默认值填充?还是跳过这条记录?还是直接报错?

我建议在消费端做两层校验:

// 第一层:Schema 校验(强校验)
if (!schemaValidator.isValid(record)) {
    // 记录异常,发送告警
    metricsCollector.increment("contract.violation");
    // 根据策略决定是否拒绝
    if (strategy == STRICT) {
        throw new ContractViolationException("数据不符合契约");
    }
}

// 第二层:业务规则校验(弱校验)
if (record.getAmount() < 0) {
    // 记录告警,但不拒绝
    logger.warn("金额为负,记录ID: {}", record.getId());
    metricsCollector.increment("business.rule.warning");
}
我的建议: 消费端一定要有「熔断机制」。如果连续 N 条数据都违反契约,说明生产者可能出了大问题。这时候应该暂停消费,避免脏数据污染下游数仓。我在项目中设置的是:连续 10 条违反,自动暂停管道并通知值班人员。

3.4 监控阶段:SLA、指标、告警

契约发布之后,不能放任不管。监控阶段要回答三个问题:

  • 生产者是否履约? 数据延迟、完整性、Schema 一致性。
  • 消费者是否合规? 有没有绕过契约直接读表?有没有使用过期版本?
  • 契约本身是否健康? 有没有长期未更新的契约?有没有无人消费的僵尸契约?

我常用的监控指标:

指标 计算方式 告警阈值
Schema 合规率 符合契约的记录数 / 总记录数 < 99.9% 告警
数据延迟 当前时间 - 数据产生时间 > 5min 告警
契约版本覆盖率 使用最新版本的消费者数 / 总消费者数 < 80% 告警
僵尸契约率 30 天无消费的契约数 / 总契约数 > 20% 告警
我曾经踩过的坑: 监控指标设得太松。Schema 合规率告警阈值设成 95%,结果每天 5% 的脏数据流入数仓,累积了一个月才发现。后来我改成 99.9%,并且加了「连续 3 个周期违规」才触发告警,避免误报。

3.5 退役阶段:通知、迁移、清理

契约也有退休的一天。字段废弃、表下线、系统重构,都需要走退役流程。

退役阶段最容易出问题。我见过一个团队直接删了生产者的表,下游十几个消费者同时报错,数据管道全线崩溃。

正确的退役流程:

  1. 标记为 DEPRECATED: 在 Registry 中将契约状态改为 DEPRECATED,保留数据,但开始告警。
  2. 通知所有消费者: Registry 自动查询所有绑定关系,发送迁移通知。给一个明确的截止日期(比如 30 天后)。
  3. 灰度下线: 先停止写入新数据,观察消费者是否还有依赖。确认无误后,再清理元数据。
  4. 归档: 将契约历史版本归档,保留至少 6 个月,方便回溯。
我的习惯: 退役阶段一定要有「回滚按钮」。如果消费者迁移失败,可以快速恢复数据生产。我在项目中保留了一个 7 天的缓冲期——标记 DEPRECATED 后的 7 天内,生产者仍然可以写入,只是会打上「已废弃」标签。

退役完成后,记得清理监控指标和告警规则。不然你会收到一堆「僵尸契约」的告警,浪费精力。


数据契约的生命周期,说白了就是「从生到死」的管理流程。每个阶段都有明确的输入、输出和检查点。设计时想清楚,发布时自动化,消费时做校验,监控时看指标,退役时走流程——做到这五点,契约才能真正保障数据质量,而不是变成另一份没人看的文档。