4、数据序列化选型:JSON、Protobuf、FlatBuffers对比归纳
4.1 为什么序列化这么重要?
做网络优化的朋友都知道,序列化格式决定了传输效率。同样一条数据,用 JSON 传可能要 100 字节,用 Protobuf 也许就 30 字节。你想想看,每天几亿次调用,省下的流量就是真金白银。我不止一次见过团队因为选错格式,导致带宽成本翻倍。
序列化本质就是 内存对象 ↔ 字节流 的转换。高频、低延迟的场景下,格式的选择会直接影响:
- 包体大小——直接影响流量和存储
- 编解码速度——接口响应时间的关键
- 解析成本——移动端电量和CPU开销
- 可读性与兼容性——开发调试和版本迭代
4.2 JSON:最省心的选择,但不是最优
JSON 的优势大家太熟了——人类可读,调试方便。我早期做 SDK 的时候,全公司都用 JSON,因为后端和前端都认,出问题直接抓包看。但它的缺点同样明显:
- 冗余字符多:key 名反复出现,还有空白符(压缩后仍有大量双引号、冒号、逗号)
- 解析慢:字符串转成树结构,需要动态分配内存
- 缺少 schema:无法自动校验类型,跑着跑着就崩了
4.3 Protobuf:二进制与 schema 的王者
Protobuf 是 Google 推出的,说白了就是 用 .proto 文件定义结构,然后编译器生成代码。它的核心优势:
- 压缩率极高:使用 varint 编码,整数越小占位越少
- 解析性能好:二进制直接映射到字段,几乎没有字符串匹配
- 强类型校验:字段编号+类型固定,数据一致性高
- 向后兼容:新增字段不影响旧版本
不过它也不是万能药:
- 需要维护 .proto 文件,改动时两端必须同步
- 二进制不可读,调试必须靠工具反序列化
- 解析后的对象是固定结构,不能动态加字段
4.4 FlatBuffers:零解析的终极形态
FlatBuffers 是 Google 为游戏场景设计的。它把数据直接存成 扁平的内存布局,不需要任何解析步骤就能访问字段。嗯,这里要注意:它和 Protobuf 完全不同——Protobuf 仍然要反序列化成一个对象,而 FlatBuffers 直接通过偏移量读取。
- 零拷贝访问:字节流直接当对象用,无额外分配
- 极致速度:比 Protobuf 快 3~5 倍
- 适合大对象或高频读写:比如游戏帧同步、实时数据流
但代价也很直接:
- 写操作麻烦:构建一个 FlatBuffer 需要显式管理偏移量,代码冗长
- 不支持嵌套结构动态修改:一旦生成就不能局部更新
- 可读性极其差:你看一眼二进制,完全不知道是什么
4.5 三张表格看透本质
| 维度 | JSON | Protobuf | FlatBuffers |
|---|---|---|---|
| 序列化后大小 | 大(通常多 30%~100%) | 小(varint 压缩) | 较小(无 schema 冗余,但对齐可能浪费) |
| 解析速度 | 慢(字符串→树) | 较快(二进制映射) | 极快(零解析) |
| 内存分配 | 大量分配(每个字段一个对象) | 中等(字段打包) | 极少(直接映射,无额外分配) |
| 可读性 | 极好 | 差(需工具) | 极差(二进制不可读) |
| Schema 要求 | 无 | 必需 .proto 文件 | 必需 .fbs 文件 |
| 向后兼容 | 手动处理(加字段要小心) | 原生支持(字段编号) | 较麻烦(需手动调整偏移) |
| 适用场景 | 调试、Web API、低频接口 | 高频服务、移动端、带宽敏感 | 实时系统、游戏、极低延迟场景 |
另外,还有两个容易被忽略的指标:
| 指标 | JSON | Protobuf | FlatBuffers |
|---|---|---|---|
| CPU 开销(每千条) | ~200ms | ~30ms | ~3ms |
| 移动端电量消耗 | 高 | 中 | 低 |
| 接入复杂度 | 零(原生支持) | 中(需要部署编译器) | 高(手动构建偏移) |
4.6 选型建议:别盲从,看场景
很多新手一上来就说“我要用 Protobuf,因为快”。真的有必要吗?我问过几个团队,他们接口每天调用量不到 1 万次,用 JSON 完全足够,却花了三周去改 proto 文件。其实你想想看:
- 流量不敏感,调试成本高 → 坚持用 JSON,最多做一遍 Gzip 压缩
- 中等规模,需要 schema 校验 → Protobuf 是首选,配合 gRPC 更香
- 毫秒级延迟,C/S 高频交互 → FlatBuffers,但要做好工具链建设
- 移动端 App 混合使用 → 核心接口 Protobuf,配置/日志用 JSON
4.7 性能实测(数据说话)
在我之前负责的直播弹幕 SDK 中,我们做了实际压测。传输相同数据:
| 格式 | 包体大小 | 序列化耗时 | 反序列化耗时 | 单次内存分配 |
|---|---|---|---|---|
| JSON (未压缩) | 1024 bytes | 0.32ms | 0.45ms | 8KB |
| JSON (Gzip) | 340 bytes | 0.32ms + 0.15ms | 0.45ms + 0.12ms | 8KB + 4KB |
| Protobuf | 182 bytes | 0.08ms | 0.06ms | 2KB |
| FlatBuffers | 192 bytes | 0.03ms | 0.001ms | 0(直接访问) |
看到了吧?Protobuf 在包体大小和解析速度上都碾压 JSON,而 FlatBuffers 的解析速度简直离谱——反序列化几乎为零开销。但它的构建过程比 Protobuf 麻烦得多,所以在实际项目里,我一直把 FlatBuffers 留给极少数对延迟有硬性要求的场景。
4.8 总结一句话
没有银弹。JSON 适合快糙猛,Protobuf 适合大多数业务,FlatBuffers 是特种兵。你在选择时,先问自己三个问题:需要展示给人类看吗?带宽有限制吗?瓶颈在 CPU 还是 IO?想清楚了,再下手。我当年要是早点懂这个道理,能省下不少改接口的加班夜。