Schema 基础概念:数据世界的骨架
聊流批一体之前,咱们得先把地基打牢。这个地基就是 Schema。
我记得刚入行那会儿,带我的老大哥跟我说过一句话,我一直记到现在:「没有 Schema 的数据,就是一盘散沙。」当时我不太理解,觉得数据不就是存起来嘛,要什么自行车。直到后来我亲手把一个没有 Schema 约束的 Kafka topic 搞崩过——嗯,那又是另一个故事了。
什么是 Schema?
说白了,Schema 就是数据的「说明书」。它告诉你:
- 数据长什么样
- 每个字段叫什么名字
- 字段是什么类型(整数、字符串、时间戳……)
- 字段之间有没有约束关系
举个最简单的例子。你写一个 JSON:
{
"user_id": 12345,
"name": "张三",
"age": 28,
"signup_time": "2024-03-15T10:30:00Z"
}
这个 JSON 本身没有 Schema 声明。但如果你用 Avro 定义一下:
{
"type": "record",
"name": "User",
"fields": [
{"name": "user_id", "type": "int"},
{"name": "name", "type": "string"},
{"name": "age", "type": "int"},
{"name": "signup_time", "type": "string", "logicalType": "timestamp-millis"}
]
}
这就是 Schema。它明确告诉所有消费方:user_id 必须是整数,signup_time 必须是时间戳格式。谁要是传个字符串的 user_id 进来,对不起,解析直接报错。
Schema 在数据系统中的角色
你想想看,一个典型的数据系统里,数据要经过多少环节?
采集 → 传输 → 存储 → 计算 → 输出
每个环节都可能对数据做「手脚」。没有 Schema 会怎样?
- 采集端:字段名随意改,今天叫 user_name,明天叫 username,下游全挂
- 传输端:序列化/反序列化全靠猜,性能差还容易出错
- 存储端:列式存储不知道列类型,压缩率上不去
- 计算端:Spark 读 Parquet 文件,没有 Schema 就得全表扫描推断类型
- 输出端:BI 报表字段对不上,业务方天天找你喝茶
我在项目中遇到过最典型的场景:一个实时数仓项目,Kafka 里的 JSON 日志字段类型不固定。某天业务方在 age 字段里塞了个 "unknown" 字符串,下游 Flink 任务直接抛异常,整个 pipeline 停了 40 分钟。原因?没有 Schema 校验。
所以 Schema 在数据系统里扮演的角色,我总结为三个:
| 角色 | 说明 | 类比 |
|---|---|---|
| 契约 | 上下游约定数据格式,谁改谁负责 | API 接口文档 |
| 蓝图 | 告诉系统数据怎么存、怎么读、怎么优化 | 建筑图纸 |
| 护栏 | 在数据进入系统前拦截异常格式 | 高速公路护栏 |
结构化数据 vs 半结构化数据:Schema 的差异
这个问题,很多同学容易搞混。咱们掰开揉碎讲清楚。
结构化数据
典型代表:关系型数据库的表(MySQL、PostgreSQL)。
特点:
- Schema 是强制的,必须先定义表结构,才能写入数据
- 字段类型严格:INT 就是 INT,VARCHAR(100) 就是 100 个字符
- 支持约束:主键、外键、唯一索引、非空
- 修改 Schema 成本高:ALTER TABLE 可能锁表
举个例子:
CREATE TABLE users (
user_id INT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
age INT CHECK (age >= 0 AND age <= 150),
signup_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
这种 Schema 的好处是「确定性」极强。你读出来的数据,字段类型、长度、范围都是确定的。坏处是「灵活性」差——业务变了,加个字段可能要 DBA 审批三天。
半结构化数据
典型代表:JSON、XML、Avro、Parquet(带 Schema 的也算半结构化)。
特点:
- Schema 可以是隐式的(JSON 本身不声明类型)
- 字段可以缺失、可以嵌套、可以类型不一致
- 修改成本低:加个字段,直接写进去就行
- 但解析成本高:每个读取方都要自己处理类型推断
还是那个用户数据,用 JSON 写:
{
"user_id": "12345", // 注意:这里是字符串
"name": "张三",
"age": "28", // 这里也是字符串
"signup_time": "2024-03-15T10:30:00Z",
"extra_field": "随便加" // 随时可以加新字段
}
看到了吗?user_id 和 age 都变成了字符串。下游如果按整数解析,直接报错。这就是半结构化的「双刃剑」——灵活,但容易出幺蛾子。
核心差异对比
| 维度 | 结构化数据 | 半结构化数据 |
|---|---|---|
| Schema 定义时机 | 写入前(Schema-on-Write) | 读取时(Schema-on-Read) |
| 类型约束 | 严格,写入即校验 | 松散,读取时推断 |
| 字段可变性 | 低,需 DDL 操作 | 高,随时增减字段 |
| 存储效率 | 高(定长字段、列式压缩) | 低(字段名重复存储) |
| 查询性能 | 高(预编译、索引友好) | 低(需解析、类型转换) |
| 适用场景 | OLTP、核心交易系统 | 日志、事件流、数据湖 |
流批一体下的 Schema 挑战
好,现在我们把话题拉回流批一体。为什么 Schema 在流批一体场景下特别重要?
因为流和批对 Schema 的「容忍度」完全不同:
- 批处理:读一次全量数据,Schema 不匹配?大不了重跑。时间成本高,但能接受。
- 流处理:数据源源不断进来,Schema 不匹配?直接报错,数据就丢了。没有「重跑」这个选项。
你想想看,同一个数据 pipeline,既要跑批(比如 T+1 的全量聚合),又要跑流(比如秒级的实时指标)。如果 Schema 不一致,批跑出来 100 万行,流跑出来 95 万行——业务方该信谁?
所以流批一体对 Schema 的要求,我总结为三点:
- 统一:流和批用同一套 Schema 定义,不能各搞各的
- 可演化:业务在变,Schema 也要变,但不能破坏兼容性
- 可回溯:任何时候都能查到某个时间点的 Schema 版本,方便历史数据重跑
这三点,就是后面几章要深入讲的内容。今天先把概念理清楚。
本章知识体系
下面这张图,把 Schema 的核心概念串起来了。我建议你保存下来,后面讲到 Schema Registry、兼容性策略时,可以回来对照着看。
这张图把今天讲的内容串起来了。从 Schema 是什么,到它在系统里的三个角色,再到结构化与半结构化的差异,最后落到流批一体的三大要求。后面的章节,我们会围绕「统一、可演化、可回溯」这三个点,一步步展开。