3. Schema基础概念

在行情数据系统中,Schema 不是一份“可选的文档”,而是数据生产方与消费方之间的数据契约。它规定了每条行情记录有哪些字段、字段的类型与精度、哪些字段必填、字段的语义边界,以及数据在序列化与反序列化时应遵循的规则。理解 Schema 的定义、结构与元数据,是后续处理 Schema 演进与兼容问题的前提。

一、Schema 的定义与结构

Schema 是对数据结构的形式化描述。以行情快照为例,一条记录通常包含标的代码、交易所、时间戳、买卖盘口、最新价、成交量等字段。Schema 需要回答三个层面的问题:结构层(有哪些字段、嵌套关系如何)、类型层(每个字段是整型、浮点、字符串还是枚举)、约束层(取值范围、精度、是否可空、默认值)。

一个典型的行情 Schema 结构可以抽象为:

MarketTick
├── symbol: string (required)
├── exchange: enum {SSE, SZSE, HKEX} (required)
├── ts: int64 (epoch millis, required)
├── bids: array<BidAsk> (max 10)
│   ├── price: decimal(18,4)
│   └── volume: int64
├── asks: array<BidAsk> (max 10)
└── last: decimal(18,4) (nullable)

可以看到,Schema 不只是字段清单,还携带了嵌套、数组容量、精度和可空性等约束。这些约束直接决定了序列化后的字节布局,也决定了上下游系统能否正确解析。

二、元数据与数据契约

Schema 的元数据(Metadata)是描述“描述本身”的信息,常见包括:Schema 版本号、命名空间、作者、创建时间、兼容性策略(如 BACKWARD、FORWARD、FULL)、字段的默认值与别名。元数据让 Schema 具备可治理性——没有版本号和兼容策略的 Schema,在演进时几乎必然引发线上事故。

数据契约 = Schema 结构 + 元数据 + 兼容性约定。行情系统中,生产方(交易所接入、行情网关)与消费方(策略、风控、回测)通过契约解耦:只要契约不变,双方可以独立发布。

在行情场景中,契约的破坏往往来自看似无害的改动:把 price 从 float 改为 double、删除一个盘口字段、把必填改为可空。这些改动如果没有元数据中的兼容策略约束,消费方会在反序列化时直接抛异常或读到错误值。

三、主流模式语言概述

不同模式语言在表达力、序列化效率、演进能力上各有取舍。行情系统常见的选择如下表:

模式语言 表达形式 序列化 演进能力 行情场景适用性
JSON Schema JSON 文本 文本,体积大 弱,靠约定 配置、REST 接口、调试
Avro JSON 定义 + 二进制 紧凑二进制 强,支持读写 Schema 解析 Kafka 行情管道、批处理
Protobuf .proto IDL 紧凑二进制 强,字段编号 + 保留位 低延迟行情分发、gRPC
Thrift .thrift IDL 二进制/压缩 中,需版本管理 跨语言服务、内部 RPC

JSON Schema 胜在可读与通用,但缺乏强制的二进制契约,行情高频场景下体积和解析开销偏大。Avro 的核心优势是读写 Schema 分离:写入时用写 Schema,读取时用读 Schema,二者通过字段名和默认值做解析,天然支持字段增删。Protobuf 通过字段编号(tag)保证兼容,删除字段时使用 reserved 防止编号复用,适合对延迟敏感的行情分发。Thrift 与 Protobuf 类似,但演进规则更依赖团队纪律。

四、案例与常见误区复盘

某行情网关最初用 JSON 传输逐笔成交,字段为 {"sym":"600000","px":10.25,"qty":100}。随着标的数量增长,带宽和 GC 压力上升,团队迁移到 Protobuf。迁移中出现的典型问题值得复盘:

  • 字段编号复用:删除旧字段 px 后,新字段 price 复用了编号 2,导致旧消费方把价格解析成数量。正确做法是 reserved 2;。
  • 默认值语义混淆:Protobuf 3 中数值字段无法区分“未设置”和“0”,行情中价格为 0 与缺失含义完全不同,需用 optional 或 wrapper 类型。
  • 精度丢失:用 float 表示价格,在 10.25 等十进制小数上出现舍入误差,应改用定点整数或 decimal 字符串。
  • 缺少兼容策略:Schema Registry 未配置兼容级别,一次“加必填字段”的变更直接让下游反序列化失败。
实践建议:行情 Schema 变更前,先在 Schema Registry 中做兼容性检查;对价格、数量等关键字段固定精度与类型;删除字段必须保留编号或名称;所有变更附带版本号与变更说明。
不要把 Schema 当成“写完就不动”的静态文件。行情业务变化快,Schema 演进是常态;真正危险的不是变更本身,而是没有契约约束和兼容验证的随意变更。
行情数据 Schema 契约结构 结构层 字段 / 嵌套 / 数组 bids / asks / last symbol / exchange / ts 类型与约束层 int64 / decimal(18,4) enum / nullable / default 精度 / 范围 / 必填 元数据层 版本号 / 命名空间 兼容策略 / 别名 作者 / 变更说明 数据契约:生产方(行情网关) ⇄ 消费方(策略 / 风控 / 回测) 契约不变 → 双方独立发布;契约变更 → 必须经过兼容性验证 JSON Schema 可读 / 弱演进 Avro 读写Schema分离 Protobuf 字段编号 / reserved Thrift 跨语言 RPC

综上,Schema 基础概念的核心在于:结构定义数据长什么样,元数据定义数据如何被治理,模式语言决定数据如何被序列化与演进。只有把三者统一为数据契约,行情系统才能在频繁变更中保持稳定与兼容。