3、数据格式与序列化:JSON、CSV、Protobuf、Avro、Parquet 的对比与选择
做数据录制回放,说白了就是在跟「格式」打交道。
你录下来的数据,用什么装?怎么存?怎么读?这直接决定了你的系统能跑多快、能撑多大。我见过不少团队,前期图省事选了 JSON,结果到了回放阶段,性能瓶颈卡得死死的,最后不得不重构。嗯,今天我们就来把这几种主流格式掰开揉碎讲清楚。
3.1 为什么数据格式这么重要?
数据格式决定了三件事:
- 体积:同样的数据,不同格式能差出 10 倍以上
- 速度:序列化/反序列化的开销,直接影响录制和回放的吞吐
- 兼容性:字段增删、类型变化,能不能平滑演进
我个人习惯,选格式之前先问自己三个问题:
- 数据是给机器读,还是给人看?
- 数据量级是 KB 级,还是 TB 级?
- Schema 会不会频繁变化?
答案不同,选择天差地别。
3.2 五种格式速览
| 格式 | 类型 | 可读性 | 压缩率 | 序列化速度 | Schema 支持 | 典型场景 |
|---|---|---|---|---|---|---|
| JSON | 文本 | ★★★★★ | ★☆☆☆☆ | ★★☆☆☆ | 弱(无强制) | API 调试、小规模数据 |
| CSV | 文本 | ★★★★☆ | ★★☆☆☆ | ★★★☆☆ | 无 | 日志导出、Excel 交换 |
| Protobuf | 二进制 | ★☆☆☆☆ | ★★★★☆ | ★★★★★ | 强(.proto 文件) | 微服务 RPC、高性能录制 |
| Avro | 二进制 | ★★☆☆☆ | ★★★★☆ | ★★★★☆ | 强(内嵌 Schema) | Kafka 消息、数据管道 |
| Parquet | 列式二进制 | ★☆☆☆☆ | ★★★★★ | ★★★☆☆ | 强(内嵌 Schema) | 大数据分析、数仓存储 |
核心结论:没有银弹。选格式就是做 trade-off。
3.3 JSON:最灵活,也最「贵」
JSON 的好处不用多说——人类可读,任何语言都能解析。我刚开始做录制系统时,第一版用的就是 JSON。调试起来确实方便,curl 一把梭。
但问题也很明显:
- 体积大:每个字段名都要重复出现,一个简单的
{"user_id": 123, "action": "click"}就要 30 多个字节 - 解析慢:字符串转数字、转布尔,每一步都有开销
- 无 Schema 约束:字段名拼错了也不会报错,回放时才发现数据对不上
避坑指南:我曾经在一个高吞吐录制项目里用 JSON,结果单机每秒只能处理 2 万条。换成 Protobuf 后,直接飙到 15 万条。嗯,血的教训。
3.4 CSV:简单粗暴,但坑不少
CSV 适合什么?日志导出、给业务同学看的数据快照。你想想看,用 Excel 打开 CSV 直接就能分析,多方便。
但做录制回放时,CSV 有几个致命伤:
- 没有类型信息:所有字段都是字符串,回放时得自己转类型
- 嵌套结构不支持:一个字段里如果有数组或对象,CSV 就抓瞎了
- 转义规则混乱:字段里带逗号或换行符,解析器可能直接崩掉
我个人建议:CSV 只用于「一次性导出」或「人工检查」,别用在核心录制链路上。
3.5 Protobuf:高性能录制的首选
Protobuf 是 Google 搞的,说白了就是「定义好数据结构,然后生成代码,序列化时直接写二进制」。
先看一个简单的 .proto 定义:
syntax = "proto3";
message RecordEvent {
int64 timestamp = 1;
string user_id = 2;
string action = 3;
map<string, string> properties = 4;
}
编译后,序列化出来的二进制体积只有 JSON 的 1/3 到 1/5。而且解析速度极快——不需要字符串解析,直接按偏移量读内存。
我为什么推荐它做录制?
- 强类型:字段类型写死了,不会出现「这个字段到底是 int 还是 string」的尴尬
- 向前向后兼容:加字段时用 optional,老代码读到新数据也不会崩
- 跨语言:Java、Go、Python、C++ 都能生成对应的代码
小技巧:录制时可以用 Protobuf 的 FieldMask 特性,只录制需要的字段,进一步减少体积。
3.6 Avro:数据管道的瑞士军刀
Avro 和 Protobuf 很像,但有一个关键区别:Schema 和数据一起存。
什么意思?Avro 文件里,开头就写明了 Schema。读数据时,解析器自动知道每个字段叫什么、什么类型。这在数据管道里特别有用——你不需要提前约定好 .proto 文件,拿到文件就能读。
举个例子,Avro 的 Schema 定义:
{
"type": "record",
"name": "RecordEvent",
"fields": [
{"name": "timestamp", "type": "long"},
{"name": "user_id", "type": "string"},
{"name": "action", "type": "string"}
]
}
Avro 在 Kafka 生态里用得特别多。我记得有一次帮客户搭实时录制管道,数据从 Kafka 进来,直接序列化成 Avro,下游 Flink 消费时自动解析 Schema,完全不用手动维护版本。
但 Avro 也有缺点:
- Schema 本身占空间,小数据量时反而比 Protobuf 大
- 解析时多了一步 Schema 读取,速度略慢于 Protobuf
3.7 Parquet:大数据分析的王者
Parquet 是列式存储格式。你想想看,传统行式存储(JSON、CSV)读数据时,哪怕你只想要一个字段,也得把整行读进来。Parquet 不一样——它按列存,读哪列取哪列。
这对回放场景意味着什么?
- 查询快:如果你只回放某个用户的记录,Parquet 可以只扫描相关列
- 压缩率高:同一列的数据类型相同,压缩算法效果极好
- 适合批量:单条写入慢,但批量写入和读取非常快
我一般在做「历史数据回放」时用 Parquet。比如录制了 1TB 的原始数据,需要按时间范围回放分析,Parquet 配合 Spark 或 Presto,查询速度能比 CSV 快 10 倍以上。
注意:Parquet 不适合实时录制。它的写入开销大,单条追加性能很差。实时录制用 Protobuf 或 Avro,归档分析时再转 Parquet。
3.8 如何选择?一张流程图帮你理清
下面这张图是我自己总结的选型逻辑,你可以照着走一遍:
3.9 实战建议:我的「三段式」策略
做了这么多年数据录制回放,我总结了一套比较稳的套路:
- 录制阶段:用 Protobuf。性能优先,Schema 提前定义好,生成代码后直接序列化写入文件或消息队列
- 传输阶段:用 Avro。Kafka 或 Pulsar 里走 Avro,Schema Registry 自动管理版本,上下游解耦
- 分析阶段:转 Parquet。用 Spark 或 Flink 批量转换,然后丢到数仓里做 OLAP 查询
小提示:如果你用的是云服务,比如 AWS 的 S3 + Athena,Parquet 几乎是必选。Athena 按扫描数据量计费,Parquet 能帮你省 60% 以上的费用。
3.10 总结
数据格式没有绝对的好坏,只有合不合适。
- 要可读性,选 JSON/CSV
- 要性能,选 Protobuf
- 要管道兼容,选 Avro
- 要分析效率,选 Parquet
你想想看,如果你的录制系统每秒要处理 10 万条事件,用 JSON 就是给自己挖坑。反过来,如果只是调试几个接口,上 Protobuf 反而有点杀鸡用牛刀。
嗯,选型这事,多踩几次坑就明白了。