4、Schema 即契约:Avro/Protobuf/JSON Schema 在契约中的应用、Schema Registry 的作用

好,咱们进入第四章节。前面聊了数据契约的概念和角色,那契约到底长什么样?用什么语言写?

说白了,Schema(模式)就是契约的实体。你想想看,一份数据契约,核心不就是对数据结构的约束吗?字段叫什么、什么类型、是否必填、取值范围是多少——这些信息,就是通过 Schema 来描述的。

我个人习惯把 Schema 比作“数据世界的接口文档”。就像写代码要定义接口一样,数据交换也得先定好 Schema。没有它,生产者和消费者之间就是鸡同鸭讲。

4.1 三大 Schema 语言:Avro / Protobuf / JSON Schema

目前业界主流的 Schema 语言有三类:Apache Avro、Protocol Buffers(Protobuf)和JSON Schema。它们各有各的脾气,选型时得看场景。

4.1.1 Avro:大数据生态的“亲儿子”

Avro 是 Hadoop 生态里长出来的。它的特点是Schema 和数据文件绑在一起。读数据时,Schema 就嵌在文件头里,不需要额外去拿。

我在做实时数仓项目时遇到过一个问题:Kafka 里的消息用 Avro 序列化,消费者拿到二进制流,只要读到 Schema ID,就能反序列化出完整对象。这比 JSON 省带宽多了。

Avro 的核心优势:

  • Schema 自带在数据文件里,自描述性强
  • 支持 Schema 演化(向前/向后兼容)
  • 序列化后体积小,适合大数据批量传输
  • 与 Kafka、Spark、Flink 集成度极高

一个典型的 Avro Schema 长这样:

{
  "type": "record",
  "name": "User",
  "namespace": "com.example",
  "fields": [
    {"name": "userId", "type": "string"},
    {"name": "userName", "type": "string"},
    {"name": "age", "type": ["null", "int"], "default": null},
    {"name": "email", "type": "string"}
  ]
}

注意那个 age 字段,用了联合类型 ["null", "int"],还给了默认值。这就是 Avro 处理 Schema 演化的手段——加字段时给个默认值,老消费者就不会崩。

4.1.2 Protobuf:微服务间的“性能之王”

Protobuf 是 Google 家的。它比 Avro 更轻量,序列化后的二进制流更短。微服务之间 RPC 调用,Protobuf 几乎是标配。

我记得有一次帮客户优化接口性能,把 JSON 换成 Protobuf,响应时间直接降了 40%。为什么?因为 Protobuf 不需要解析字段名,它用数字编号(field number)来标识字段。

syntax = "proto3";

message User {
  string user_id = 1;
  string user_name = 2;
  optional int32 age = 3;
  string email = 4;
}

这里 = 1、= 2 就是字段编号。一旦发布,编号不能改。我见过有人把编号从 1 改成 2,结果新旧版本对不上,数据全乱了。嗯,这是个坑。

避坑指南:

我曾经在一个项目里看到团队把 Protobuf 字段编号随意重排,导致线上数据反序列化失败。记住:字段编号是契约的一部分,发布后只增不减,不能复用已删除的编号。

4.1.3 JSON Schema:Web API 的“通用语言”

JSON Schema 跟前面两个不一样。它不是序列化格式,而是纯校验规则。你定义好 Schema,任何 JSON 数据都可以拿它来校验。

它的优势是人类可读。前端、后端、甚至业务人员都能看懂。适合 REST API、配置文件、事件消息等场景。

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "userId": {"type": "string"},
    "userName": {"type": "string"},
    "age": {"type": "integer", "minimum": 0, "maximum": 150},
    "email": {"type": "string", "format": "email"}
  },
  "required": ["userId", "userName", "email"]
}

你看,age 字段还能加 minimum 和 maximum 约束。这在 Avro 和 Protobuf 里做不到。但 JSON Schema 的缺点是没有序列化能力,数据还是得传 JSON 文本,体积大。

4.2 三种 Schema 的对比

我整理了一张表,方便你快速对比:

维度 Avro Protobuf JSON Schema
序列化格式 二进制 二进制 文本(JSON)
Schema 存储 嵌入数据文件 独立 .proto 文件 独立 .json 文件
Schema 演化 原生支持(强) 支持(需谨慎) 需手动管理
性能 高 极高 低
可读性 低 低 高
适用场景 大数据、Kafka 微服务、gRPC REST API、配置文件

选型时我的建议是:看你的数据流终点在哪。如果数据最终进 Hadoop/Spark,选 Avro;如果走 gRPC 微服务,选 Protobuf;如果是 Web API 对外暴露,JSON Schema 更友好。

4.3 Schema Registry:契约的“中央档案馆”

有了 Schema,怎么管理?怎么保证生产者和消费者用的是同一个版本?

这就轮到 Schema Registry 登场了。它是个集中式服务,专门存储、版本化、校验 Schema。

4.3.1 它解决了什么问题?

我举个真实例子。之前有个团队,Kafka 里跑着用户订单数据。生产者改了 Schema,加了个 discount 字段,但忘了通知消费者。结果消费者反序列化时直接报错,订单处理停了 2 小时。

如果有 Schema Registry,这种情况根本不会发生。因为生产者注册新 Schema 时,Registry 会检查兼容性。不兼容?直接拒绝写入。

Schema Registry 的核心职责:

  • 存储:保存所有版本的 Schema
  • 版本管理:每次变更生成新版本号
  • 兼容性校验:检查新 Schema 是否破坏已有消费者
  • Schema ID 分发:给每个 Schema 分配唯一 ID,序列化时带上 ID 即可

4.3.2 兼容性策略

Registry 支持多种兼容性策略。我常用的有这几种:

策略 说明 适用场景
BACKWARD 新 Schema 能读老数据(可删字段,不可增必填字段) 消费者先升级
FORWARD 老 Schema 能读新数据(可增字段,不可删字段) 生产者先升级
FULL 同时满足 BACKWARD 和 FORWARD 生产者和消费者同时升级
NONE 不校验兼容性 开发测试环境

我个人习惯在生产环境用 BACKWARD 策略。为什么?因为大多数场景下,消费者先升级更安全。你想想看,消费者升级后,既能读老数据也能读新数据,生产者可以慢慢改。

4.3.3 工作流程

下面这张图展示了 Schema Registry 在数据流中的位置:

Schema Registry 工作流程 生产者 消费者 Schema Registry 存储 · 版本 · 校验 Kafka Topic 1. 注册 Schema 2. 返回 Schema ID 3. 发送数据 + Schema ID 4. 拉取数据 + Schema ID 5. 获取 Schema 核心:数据中只带 Schema ID,不带完整 Schema。消费者通过 ID 从 Registry 获取 Schema 反序列化。 兼容性校验在步骤 1 完成,不兼容的 Schema 无法注册,从源头杜绝数据污染。

流程其实不复杂:生产者先注册 Schema,拿到 ID;发数据时带上 ID;消费者收到数据,用 ID 去 Registry 取 Schema,然后反序列化。整个过程,数据里只带一个几字节的 ID,而不是几百字节的完整 Schema,带宽省了不少。

4.4 主流 Schema Registry 实现

目前最常用的是 Confluent Schema Registry,跟 Kafka 深度集成。另外还有 Apicurio Registry(红帽出品)和 Hortonworks Schema Registry。

我大部分项目都用 Confluent 的。它支持 Avro、Protobuf、JSON Schema 三种格式,兼容性策略也最全。部署时注意:Registry 本身需要高可用,因为它一旦挂了,生产者和消费者都拿不到 Schema,整个数据管道就瘫了。

我的经验:

Schema Registry 最好跟 Kafka 集群走同一个机房,延迟控制在 5ms 以内。另外,记得给 Registry 配缓存,不然高并发下容易成为瓶颈。

4.5 小结

这一章我们聊了 Schema 作为契约的三种主流语言:Avro 适合大数据、Protobuf 适合微服务、JSON Schema 适合 API。然后讲了 Schema Registry 这个“中央档案馆”怎么管理版本、校验兼容性。