引言
数据传输与存储的第一性问题是:用什么格式把对象变成字节,再变回来? 格式选择决定了三件事——体积、速度、演进能力。文本族(JSON/XML/YAML)人可读但费字节;二进制族(Protobuf/MessagePack)省空间但难调试;列式族(Parquet/ORC)为分析而生的压缩率惊人。本文给出全景对比 + 一套可落地的选型决策树。
前置:基本数据模型概念。大数据场景配合 [[database]] / [[data-engineering]] 专题。
目录
- 1. 序列化格式三大家族
- 2. 文本族:JSON、XML、YAML、TOML
- 3. 二进制族:Protobuf、MessagePack、CBOR、BSON
- 4. 列式族:Parquet 与 ORC
- 5. Schema 驱动 vs 无 Schema
- 6. 版本演进与兼容策略
- 7. 性能基准:体积与速度对比
- 8. 语言生态与工具链
- 9. 选型决策树
- 10. 速查表
- 延伸阅读
1. 序列化格式三大家族
| 家族 | 代表 | 特点 | 场景 |
|---|---|---|---|
| 文本族 | JSON / XML / YAML / TOML | 人可读、调试易、体积大 | API、配置、文档 |
| 二进制族 | Protobuf / MessagePack / CBOR / BSON | 紧凑、快、难读 | RPC、缓存、物联网 |
| 列式族 | Parquet / ORC / Arrow | 高压缩、列扫描快 | 大数据分析、数仓 |
核心取舍:可读性 ↔ 性能 ↔ 演进安全,三者最多兼顾两样。
一个数据「一生」可能跨多种格式:对象在内存(Arrow)→ RPC 线(Protobuf)→ 落盘(Parquet)——按阶段选格式。
2. 文本族:JSON、XML、YAML、TOML
| 格式 | 类型 | 可读性 | 体积 | 强项 |
|---|---|---|---|---|
| JSON | 通用数据 | ★★★ | 中 | API 事实标准、生态最广 |
| XML | 文档/配置 | ★★ | 大 | 命名空间、Schema(XSD)、工具成熟 |
| YAML | 配置 | ★★★★ | 中 | 缩进友好、可注释、锚点复用 |
| TOML | 配置 | ★★★★ | 中 | 表结构清晰、无缩进歧义 |
JSON 事实标准(API/Web/NoSQL),但有限制:
{
"user": {"name": "Alice", "age": 30, "active": true}
}
JSON 缺陷:无注释、无数值范围类型(整数/浮点混淆)、日期需约定、null 与缺失难区分。
YAML(K8s/Docker Compose 首选)——缩进与锚点:
defaults: &defaults
timeout: 30
retries: 3
service:
<<: *defaults # 锚点合并
name: auth
TOML(Rust/Cargo 风格,无缩进歧义):
[server]
host = "0.0.0.0"
ports = [8000, 8001]
| 选型 | 推荐 |
|---|---|
| Web API | JSON |
| 配置文件(人写) | TOML(简单)或 YAML(复杂树) |
| 带 schema 校验 | JSON Schema / XSD |
| 文档交换 | XML |
3. 二进制族:Protobuf、MessagePack、CBOR、BSON
Protobuf(gRPC 的默认,Schema 驱动):
message User {
int64 id = 1;
string name = 2;
bool active = 3;
repeated string tags = 4;
}
user = User(id=1, name="Alice", active=True, tags=["a", "b"])
data = user.SerializeToString() # 紧凑二进制
| 格式 | Schema | 特点 | 强项 |
|---|---|---|---|
| Protobuf | 必须(.proto) | 字段号编码、压缩强 | gRPC、微服务 |
| MessagePack | 无 | JSON 兼容的二进制 | 缓存/Redis、体积敏感 |
| CBOR | 无 | RFC 标准、流式 | 物联网、CoAP |
| BSON | 无 | MongoDB 原生 | 文档数据库 |
MessagePack 与 JSON 等价转换:
JSON: {"name": "Alice", "age": 30}
MsgPack: 0x82 0xa4 'n' 'a' 'm' 'e' 0xa5 'A' 'l' 'i' 'c' 'e' ...
体积对比直观:同样的 {"name":"Alice"}——JSON 约 17 字节,MessagePack 约 12 字节,Protobuf 约 8 字节。
4. 列式族:Parquet 与 ORC
列式存储改变存储布局——按列而不是按行存,分析查询只读需要的列:
| 格式 | 压缩率 | 适用 | 特点 |
|---|---|---|---|
| Parquet | 极高(列编码 + 字典) | Spark/Hive/查询引擎 | 跨生态最广 |
| ORC | 极高 | Hive 原生 | 更侧重 Hive 优化 |
| Arrow | 内存列式 | 进程间零拷贝 | 不是存储,是内存布局 |
为什么列式适合分析:
行式: [Alice,30,北京] [Bob,25,上海] [Cara,28,广州]
列式: [Alice,Bob,Cara] [30,25,28] [北京,上海,广州]
↑ 查询"平均年龄"只读第二列,且同类型压缩率翻倍
Parquet 能力:嵌套结构、谓词下推(只读满足条件的块)、字典编码、Snappy/Gzip/Zstd 压缩。
选型要点:OLTP(事务)用行式,OLAP(分析)用列式——数据仓库、日志分析、特征存储必备。
5. Schema 驱动 vs 无 Schema
| 维度 | Schema 驱动(Protobuf/Avro) | 无 Schema(JSON/MsgPack) |
|---|---|---|
| 编译期检查 | ✅ 类型安全 | ❌ 运行期才错 |
| 演进 | 显式规则(字段号) | 靠约定 |
| 体积 | 小(字段号) | 大(存键名) |
| 灵活性 | 需改 .proto 重编译 | 即改即用 |
| 调试 | 难读二进制 | 直接看文本 |
Schema 驱动的关键价值——演进安全:id = 1 是永久 ID,字段改名不影响线上兼容(见下节)。
无 Schema 的灵活代价:键名每次都存(体积)、缺字段/多字段无约束、跨服务契约靠口头。
经验:API 边界用 Schema 驱动(Protobuf/Avro),内部处理/调试用无 Schema——边界要契约,内部要效率。
6. 版本演进与兼容策略
Protobuf 演进规则(跨版本兼容的黄金规范):
message User {
int64 id = 1; // 永远不要改字段号!
string name = 2;
// 新增字段:追加新号
optional string email = 3; // 老版本读到会忽略
// 已删除字段:用 reserved 保留号,防止误用
reserved 4;
}
| 场景 | 规则 |
|---|---|
| 新增字段 | 追加新字段号,用 optional |
| 删除字段 | reserved 占位,防未来复用 |
| 改名 | 字段号不变即可 |
| 改类型 | 危险!旧数据可能解析错 |
| 加枚举值 | 通常兼容,需协商 |
JSON 演进替代:加可选字段(老客户端忽略)、不加必需字段、容忍未知字段。语义版本管理契约。
7. 性能基准:体积与速度对比
(典型微服务消息,越大差异越明显)
| 格式 | 体积(相对 JSON) | 序列化速度 | 解析速度 |
|---|---|---|---|
| JSON(Gson/Jackson) | 1.0(基准) | 1.0 | 1.0 |
| MessagePack | 约 0.6 | 约 1.5× | 约 1.3× |
| Protobuf | 约 0.4 | 约 3–5× | 约 3–5× |
| Avro(含 Schema) | 约 0.45 | 约 3× | 约 3× |
| Parquet(压缩) | 约 0.15–0.3 | —(列式扫描快) | — |
差异随消息变大而放大;小消息(<100B)选型差异几乎可忽略——别为「快一点」牺牲可读性。
内存布局对比:Protobuf 无字符串池与指针开销,连续内存布局缓存友好;JSON 解析要建树,GC 压力大。
8. 语言生态与工具链
| 格式 | 主流库 | 备注 |
|---|---|---|
| JSON | Jackson / serde_json / Gson | 各语言皆有 |
| Protobuf | protoc + 各语言插件 | gRPC 配套 |
| MessagePack | msgpack 各语言 | 与 JSON 类型互通 |
| Parquet | arrow / pyarrow | 大数据生态统一 |
| Avro | Python avro / Java | Kafka 常用 |
| YAML | PyYAML / snakeyaml | K8s 配置 |
工具链建议:JSON Schema 校验 + OpenAPI 文档 = 无 Schema 时代的「轻契约」;Protobuf 用 buf 做 lint/breaking 检查。
9. 选型决策树
数据要给人看吗?
├─ 是 → JSON(API) / YAML(配置) / TOML(简单配置)
└─ 否 → 数据会跨版本长期演进吗?
├─ 是 → Protobuf(gRPC/RPC) 或 Avro(Kafka/流)
└─ 否 → 体积敏感吗?
├─ 是 → MessagePack / CBOR
└─ 否 → JSON 就行
分析型查询为主?
└─ 是 → Parquet(数仓/Spark) / ORC(Hive)
决策因子权重:可调试性 > 生态成熟度 > 性能 > 体积 > 演进复杂度——多数场景 JSON 起步,高流量边界升级 Protobuf。
10. 速查表
| 需求 | 格式 |
|---|---|
| Web API | JSON + OpenAPI |
| 配置 | TOML / YAML |
| RPC/微服务 | Protobuf + gRPC |
| Kafka/流式 | Avro + Schema Registry |
| 缓存体积敏感 | MessagePack / CBOR |
| MongoDB | BSON |
| 数仓分析 | Parquet / ORC |
| 进程内列式 | Arrow |
| 文档/多命名空间 | XML |
一句话记忆:给人看用 JSON,给机器省用 Protobuf,给分析引擎用 Parquet;三族各司其职,格式随阶段流动。
延伸阅读
- /regex-deep-dive/ — 文本处理的底层工具
- /text-processing-toolkit/ — jq 处理 JSON 的命令行实战
- [[data-engineering]] — 大数据管道的存储格式选择
- [[database]] — 存储引擎与数据类型的关系
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。