API 中的 Protobuf 与 JSON:速度、大小与兼容性
比较 API 中的 Protobuf 与 JSON:有效载荷大小、速度、可读性、工具链、版本演进,以及在真实产品中各自最适合的场景。

什么是 Protobuf 和 JSON(以及它们为何重要)
当你的 API 发送或接收数据时,需要一种数据格式——在请求和响应体中表示信息的标准化方式。该格式会被序列化(转换成字节)以在网络上传输,然后在客户端和服务器端反序列化回可用的对象。
两种常见选择是 JSON 和 Protocol Buffers(Protobuf)。它们都能表示相同的业务数据(用户、订单、时间戳、项目列表),但在性能、有线大小和开发工作流上做出不同的权衡。
JSON:人可读的文本
JSON(JavaScript Object Notation) 是一种基于文本的格式,由对象和数组等简单结构构成。它在 REST API 中很流行,因为易于阅读、易于记录,并且可以使用 curl 和浏览器 DevTools 等工具方便地检查。
JSON 普及的一个重要原因是:大多数语言都有良好支持,你可以直观地查看响应并立即理解内容。
Protobuf:带模式的紧凑二进制
Protobuf 是 Google 创建的一种二进制序列化格式。它不是发送文本,而是根据 模式(.proto 文件)发送紧凑的二进制表示。模式描述字段、类型和数值标签。
由于是二进制且由模式驱动,Protobuf 通常会产生更小的有效载荷并且解析更快——这在高请求量、移动网络或对延迟敏感的服务中很重要(常见于 gRPC,但不限于 gRPC)。
相同的数据,不同的权衡
将你发送的内容(是什么)与如何编码(如何发送)区分开很重要。一个包含 id、name 和 email 的“用户”可以用 JSON 或 Protobuf 建模。不同的是你为此付出的代价,体现在:
- 有效载荷大小(文本 vs 紧凑二进制)
- 序列化/反序列化所需的 CPU 时间
- 调试与可观测性(可读日志 vs 二进制工具)
- 兼容性与演进(非正式的 JSON 约定 vs 强制的模式)
没有放之四海皆准的答案。对于许多面向公共的 API,JSON 仍是默认选择,因为它更易访问、更灵活。对于内部服务间通信、性能敏感系统或需要严格契约的场景,Protobuf 往往更合适。本指南旨在帮助你基于约束做决定,而非基于意识形态。
API 数据如何被序列化与发送
当 API 返回数据时,它不能直接在网络上发送“对象”。必须先把它们转换为字节流。这个转换就是序列化——可以把它想象成将数据“打包”成可传输的形式。另一端,客户端做相反的操作(反序列化),将字节“解包”回可用的数据结构。
从服务器到客户端的简短流程
典型的请求/响应流程如下:
- 服务器构建响应,使用自己的内存类型(对象/结构体/类)。
- 序列化器编码该响应为一个有效载荷(JSON 文本或 Protobuf 二进制)。
- 有效载荷作为字节通过 HTTP/1.1、HTTP/2 或 HTTP/3 发送。
- 客户端接收字节,然后解码为自己的内存类型。
在“编码步骤”上,格式选择就会显现差异。JSON 编码会产生可读文本,例如 {\"id\":123,\"name\":\"Ava\"};Protobuf 编码则产生紧凑的二进制字节,没有工具无法直接读取其含义。
为什么格式会改变性能与工作流
因为每个响应都必须被打包与解包,格式会影响:
- 带宽(有效载荷大小): 更小的有效载荷能减少传输成本,对移动网络和高流量 API 有利。
- 延迟: 更少的数据传输通常意味着更快的响应,且更快的编码/解码能减少 CPU 时间。
- 开发工作流: JSON 易于在 DevTools 与日志中检查;Protobuf 通常需要生成类型与特定的解码工具。
API 风格会带来偏向
你的 API 风格常常会促使你倾向某种选择:
- REST 风格的 JSON API 通常使用 JSON,因为其广泛支持、易于
curl测试并便于记录与检查。 - gRPC 默认以 Protobuf 为中心,使用 HTTP/2 与代码生成,这与强类型的 Protobuf 消息天然契合。
你当然可以在 gRPC 中使用 JSON(通过转码),也可以在普通 HTTP 上传输 Protobuf,但栈的默认可用性——框架、网关、客户端库与调试习惯——通常会决定日常运维的便利性。
有效载荷大小与速度:通常能获得或失去什么
当人们比较 protobuf vs json 时,通常先看两个指标:有效载荷大小和编码/解码所需的时间。结论很直接:JSON 是文本,往往冗长;Protobuf 是二进制,往往更紧凑。
有效载荷大小:紧凑二进制 vs 可读文本
JSON 会重复字段名并以文本形式表示数字、布尔与结构,因此通常在传输中占用更多字节。Protobuf 使用数字标签并高效打包值,常常导致明显更小的有效载荷——尤其是对于大对象、重复字段和深度嵌套的数据。
不过,压缩会缩小差距。启用 gzip 或 brotli 时,JSON 的重复键会非常容易被压缩,因此“JSON vs Protobuf 的大小差异”在真实部署中可能变小。Protobuf 也可以压缩,但相对收益往往更小。
CPU 成本:解析文本 vs 解码二进制
JSON 解析器需要分词并验证文本、将字符串转换为数字,并处理边界情况(转义、空白、Unicode)。Protobuf 解码更直接:读取标签 → 读取类型化值。在许多服务中,Protobuf 能减少 CPU 时长与垃圾产生,从而在高负载下改善尾延迟。
网络影响:移动和高延迟链路
在移动网络或高延迟链路上,更少的字节通常意味着更快的传输和更少的无线电打开时间(这也有助于省电)。但如果响应已经很小,握手开销、TLS 与服务器处理可能占主导——此时格式选择的可见性会变小。
在自己的系统中如何基准测试
用你的真实有效载荷来测量:
- 选取具有代表性的请求/响应(小、典型、最差情况)。
- 比较:原始大小、压缩后大小(gzip/brotli)、编码/解码时间、端到端延迟。
- 在真实并发下运行测试并记录 p50/p95/p99。
这会把“序列化争论”变成你可以信赖的数据。
开发者体验:可读性、调试与日志
在开发者体验方面,JSON 往往默认占优。你几乎可以在任何地方检查 JSON 请求或响应:浏览器 DevTools、curl 输出、Postman、反向代理和纯文本日志。当出问题时,“我们到底发送了什么?”通常只需复制粘贴一下即可。
Protobuf 则不同:它紧凑且严格,但不可人读。如果你记录原始的 Protobuf 字节,你会看到 base64 或不可读的二进制。要理解有效载荷,你需要正确的 .proto 模式和解码器(例如 protoc、特定语言的工具链或服务生成的类型)。
实际中的调试工作流
使用 JSON 时,复现问题很直接:抓取日志中的有效载荷、脱敏后用 curl 重放,通常就能得到最小测试用例。
使用 Protobuf 时,你通常需要:
- 捕获二进制有效载荷(通常以 base64 编码),
- 使用正确的模式解码,
- 再次编码以重放请求。
这一步并不复杂,但前提是团队有可复现的工作流。
让 Protobuf(和 JSON)更易调试的建议
结构化日志对两种格式都有帮助。记录请求 ID、方法名、用户/账户标识和关键字段,而不是完整的请求体。
针对 Protobuf:
- 在安全的情况下,记录一个解码后且已脱敏的“调试视图”(例如 JSON 表示),与二进制并存。
- 在日志中存储模式版本或消息类型,避免“我们用的是哪个
.proto?”的困惑。 - 添加一个小脚本(或 make 目标),用于“用正确的模式解码这个 base64 有效载荷”,便于值班使用。
针对 JSON,考虑记录规范化的 JSON(稳定键顺序),以便更容易比较差异和查看事件时间线。
模式与类型安全:灵活性 vs 约束
API 不只是移动数据——它们还传递语义。JSON 与 Protobuf 最大的区别在于对这些语义的定义与强制程度。
JSON:形状灵活,解释也灵活
JSON 默认是“无模式”的:你可以发送任意对象与字段,只要看起来合理,许多客户端就会接受。
这种灵活性在早期很方便,但也会隐藏错误。常见问题包括:
- 字段不一致: 一个响应中叫
userId,另一个响应中叫user_id,或者不同代码路径漏发字段。 - 字符串化的数据: 将数字、布尔或日期作为字符串发送,例如
"42"、"true"或"2025-12-23",易产生混淆。 - 含糊的 null:
null可能代表“未知”、“未设置”或“刻意为空”,不同客户端可能有不同处理方式。
你可以为 JSON 添加 JSON Schema 或 OpenAPI 规范,但 JSON 本身并不强制消费者遵循它们。
Protobuf:通过 .proto 提供明确契约
Protobuf 要求在 .proto 文件中定义模式。该模式声明:
- 存在哪些字段,
- 它们是什么类型(string、integer、enum、message 等),
- 每个字段在网络上的标识号(数字标签)。
该契约有助于防止意外变更——比如把整数改成字符串——因为生成的代码期望特定类型。
重要的类型安全细节
在 Protobuf 中,数字保持为数字、枚举被限制在已知值范围,时间戳通常使用约定的类型表示(而不是随意字符串)。在 proto3 中,当你使用 optional 字段或包装类型时,“未设置”与默认值也能明确区分。
如果你的 API 需要精确类型和多语言间一致解析,Protobuf 提供了 JSON 通常需要约定才能达到的约束。
在不破坏客户端的情况下演进模式与架构
API 会演进:你会添加字段、调整行为、淘汰旧部分。目标是在不让消费者惊讶的情况下改变契约。
向后兼容 vs 向前兼容(通俗)
- 向后兼容: 新服务器能与旧客户端通信。旧客户端忽略不理解的字段并仍然可用。
- 向前兼容: 新客户端能与旧服务器通信。新客户端能处理缺失字段并回退到默认值。
好的演进策略通常追求两者,但向后兼容通常是最低门槛。
Protobuf:字段号才是真正的身份标识
在 Protobuf 中,每个字段都有一个编号(例如 email = 3)。编号,而不是字段名,是在线上编码时的身份标识。字段名主要用于人工与生成代码。
因此:
-
通常安全的变更
- 使用未被使用过的新编号添加可选字段。
- 添加新的枚举值(尽量不要重排已有值)。
- 将字段标记为废弃但保留其编号。
-
风险变更(通常会破坏兼容性)
- 重用字段编号 用于不同含义或类型。
- 将字段类型改为不兼容类型(例如 string → int)。
- 删除字段而不保留其编号(未来重用该编号会导致语义混淆)。
- 重命名字段在传输层是安全的,但可能破坏生成代码和下游假设。
最佳实践:对已移除的编号/名称使用 reserved 并保留变更日志。
JSON:靠约定与纪律来版本管理
JSON 没有内建模式,兼容性依赖于你的模式与实践:
- 倾向增量更改:添加新字段而不是改变已有含义。
- 将未知字段视为可忽略,缺失字段视为“使用合理默认值”。
- 避免更改类型(例如 number → string);若必须,新增字段名替代旧字段。
废弃与明确策略
提前文档化废弃计划:某字段被废弃时将支持多久、替代方案是什么。发布一个简单的版本策略(例如“增量更改为非破坏性,删除需要新主版本”)并遵守它。
跨平台的工具链与生态支持
在 JSON 与 Protobuf 之间选择,常常取决于你的 API 要运行在哪些环境——以及团队愿意维护什么。
浏览器 vs 服务端:JSON 的“默认”优势
JSON 几乎是通用的:每个浏览器和后端运行时都能解析它而无需额外依赖。在 Web 应用中,fetch() + JSON.parse() 是常规路径,代理、API 网关和可观测性工具也往往开箱理解 JSON。
Protobuf 也能在浏览器运行,但不是零成本默认。你通常需添加 Protobuf 库(或生成的 JS/TS 代码)、管理打包体积,并决定是否在浏览器端发送 Protobuf 以保持可检查性。
移动与后端 SDK:Protobuf 的长处
在 iOS/Android 以及后端语言(Go、Java、Kotlin、C#、Python 等)中,Protobuf 支持成熟。Protobuf 假设你会为每个平台使用相应库并通常从 .proto 文件生成代码。
代码生成带来的好处包括:
- 强类型模型与枚举,当客户端偏离契约时能更早报错;
- 更快的序列化库和跨服务一致的数据形状。
但也增加了成本:
- 构建步骤(在 CI 中生成代码、保持生成工件同步);
- 仓库/流程复杂度(发布共享
.proto包、版本固定)。
gRPC:强大的生态与形塑约束
Protobuf 与 gRPC 紧密相关,后者为你提供完整的工具链:服务定义、客户端存根、流式能力和拦截器。如果你在考虑 gRPC,Protobuf 是天然匹配。
如果你在构建传统的 JSON REST API,JSON 的工具生态(浏览器 DevTools、curl 友好调试、通用网关)仍然更简单——尤其是面向公共 API 与快速集成时。
在不做过早承诺的情况下原型化两种选项
如果你还在探索 API 面,原型化两种风格分别试验会很有帮助。例如,一些团队会快速搭建一个 JSON REST API 以获得广泛兼容,同时为内部服务使用 gRPC/Protobuf 来提高效率,再用真实有效载荷进行基准测试之后再确定默认方案。
(保留提及:Koder.ai 提供生成全栈应用的能力,并支持在不同平台上迭代契约,这类工具可帮助在不做大规模重构的情况下对比格式。)
运营适配性:缓存、网关与可观测性
选择 JSON 还是 Protobuf 不仅关乎有效载荷大小或速度,还影响 API 与缓存层、网关以及团队在事故期间依赖的工具的契合度。
缓存与 CDN
大多数 HTTP 缓存(浏览器缓存、反向代理、CDN)是基于 HTTP 语义优化的,而不是针对某种主体格式。只要响应是可缓存的,CDN 可以缓存任何字节。
然而,许多团队习惯在边缘看到 HTTP/JSON,因为它易于检查和排障。使用 Protobuf 时缓存仍然可行,但需注意:
- 缓存键(URL、查询参数,特别是
Vary) - 清晰的缓存可控头(
Cache-Control、ETag、Last-Modified) - 在支持多种格式时避免意外的缓存碎片化
内容协商(Content-Type 与 Accept)
如果同时支持 JSON 与 Protobuf,请使用内容协商:
- 客户端发送
Accept: application/json或Accept: application/x-protobuf - 服务器用匹配的
Content-Type响应
确保缓存理解这一点:设置 Vary: Accept,否则缓存可能储存 JSON 响应并错误地返回给期待 Protobuf 的客户端(或反之)。
网关、代理与可观测性
API 网关、WAF、请求/响应转换器和可观测性工具常常假设请求体为 JSON,用于:
- 请求验证与模式检查
- 字段级别的日志与脱敏
- 从有效载荷派生指标
- 在仪表板与跟踪查看器中调试
二进制 Protobuf 会限制这些功能,除非你的工具支持 Protobuf(或你增加解码步骤)。
混合环境的实用建议
常见模式是 边缘使用 JSON,内部使用 Protobuf:
- 公共 REST 端点:使用 JSON,以保持兼容性与可运维性;
- 内部服务间调用:使用 Protobuf(通常通过 gRPC),提高效率。
这既简化了外部集成,又能在可控环境中利用 Protobuf 的性能优势。
安全与可靠性考量
选择 JSON 或 Protobuf 会改变数据的编码与解析方式——但它不替代身份验证、加密、授权与服务端校验等核心安全需求。一个快速的序列化库并不能弥补接受不受信任输入而缺乏限制的 API。
格式选择并不是安全层
把 Protobuf 当作“更安全因为不可读”是一种错误观念。攻击者不需要你的有效载荷对人类可读来发起攻击;他们只需访问你的端点。如果 API 泄露敏感字段、接受无效状态或授权薄弱,换格式不能解决问题。
无论选择哪种序列化,都应使用 TLS 加密传输,强制授权检查,验证输入并安全记录日志。
攻击面:有效载荷、解析器与校验
两种格式共享常见风险:
- 超大有效载荷: 大的 JSON 文档或巨大的 Protobuf 消息都可能触发内存压力、解析变慢或拒绝服务。
- 解析器漏洞: 每个解析器都是代码,代码可能有漏洞。风险更多取决于你选择并维护的库,而非格式本身。
- 模式校验缺口: JSON 灵活,若不校验会接受意外字段或类型;Protobuf 提供类型约束,但仍可能接受语义上无效的数据(例如负数数量)除非显式校验。
可靠性:限制、超时与严格性
为了在负载与滥用下保持 API 可用,请给两种格式都应用相同的防护措施:
- 设置最大请求大小和最大消息大小(包括解压后的大小);
- 使用超时与取消来避免慢客户端或慢解析器消耗资源;
- 偏好严格校验:拒绝缺少关键业务字段、无效范围和未知枚举值(视场景而定);
- 小心日志记录:JSON 虽然易读,但两种格式都可能不小心记录敏感信息。
结论:"二进制 vs 文本格式"主要影响性能与易用性。安全与可靠性来自一致的限制、及时更新的依赖和明确的校验——而不是格式本身。
何时选择 JSON,何时选择 Protobuf
在 JSON 与 Protobuf 之间抉择,更在于你希望优化什么:面向人类与易用性,还是效率与严格契约。
JSON 是默认选择的场景
当你需要广泛兼容与容易排查时,JSON 通常是最安全的默认:
典型场景包括:
- 公共 API(你无法控制客户端或有第三方集成);
- 浏览器与 Web 客户端(原生支持 JSON,DevTools 易检查);
- 早期快速迭代(流程更简单,仪式更少);
- 以调试为先的工作流(可复制粘贴的请求、可读日志);
- 广泛被缓存或代理的 REST 端点(常见网关对 JSON 支持友好)。
Protobuf 发挥优势的场景
当性能与一致性比人类可读性更重要时,Protobuf 更有优势:
典型场景包括:
- 高吞吐量 API(带宽或外发成本高);
- 大量小调用(频繁的 chatty 调用),序列化开销会累积;
- 你控制所有客户端与部署的内部微服务;
- 基于 gRPC 的系统(Protobuf 几乎总是最佳选择);
- 移动或边缘环境(更小的有效载荷有助于降低延迟和耗电)。
决策问题以快速缩小选择范围
可以用这些问题快速判断:
- 谁在消费 API? 外部/多样 → JSON;受控 → Protobuf。
- 是否能控制所有客户端部署? 如果能,采用 Protobuf 更容易。
- 性能是真正瓶颈吗? 请测量:p95 延迟、CPU 与外发成本。
- 严格类型与契约重要吗? Protobuf 为你提供更多约束。
- 工具链是否成熟? 考虑代码生成、CI 校验和开发者入门成本。
如果无法抉择,“边缘 JSON、内部 Protobuf”常是务实的折中方案。
迁移策略:在 JSON 与 Protobuf 之间切换
格式迁移不是重写整个系统,而是减少对消费者的风险。最安全的做法是在迁移期间保持 API 可用并能回滚。
1) 从小处开始:一个端点或一个内部服务
选择低风险的表面区域——通常是内网服务调用或单个只读端点。这样可以验证 Protobuf 模式、生成客户端与可观测性改动,而不把整个 API 变为一次性大工程。
一个实用的第一步是为现有资源添加 Protobuf 表示,同时保持 JSON 形状不变。你会快速发现数据模型的歧义(null 与 missing、数字与字符串、日期格式),并能在模式中加以解决。
2) 并行运行 JSON 与 Protobuf(临时)
对外部 API 来说,双格式支持通常是最平滑的路径:
- 通过
Content-Type和Accept协商格式; - 如果协商在现有工具中难以实现,可暂时暴露单独端点(例如
/v2/...)。
在此期间,确保两种格式都从相同的单一事实来源(source-of-truth)生成,以避免细微的漂移。
3) 把格式变更当作产品变更来测试
计划好:
- 兼容性测试: 旧客户端对新服务器,新客户端对旧服务器;
- 契约测试: 验证必需字段、默认行为与错误响应;
- 基准测试: 测量有效载荷大小、CPU 与延迟(包含压缩与 TLS),而非仅看“线速”。
4) 文档化模式并提供示例
发布 .proto 文件、字段注释和具体的请求/响应示例(JSON 与 Protobuf),以便消费者确认他们对数据的理解是否一致。简短的“迁移指南”和变更日志能减少支持成本并加速采用。
常见问题
在 API 中,JSON 和 Protobuf 的实际区别是什么?
JSON 是一种基于文本的格式,便于阅读、记录和用常见工具测试。Protobuf 是由 .proto 模式定义的紧凑二进制格式,通常能产生更小的有效载荷并加快解析速度。
根据约束来选择:可达性与可调试性优先选择 JSON;效率与严格契约优先选择 Protobuf。
在请求/响应流程中,“序列化”和“反序列化”是什么意思?
API 传输的是字节,而不是内存对象。序列化 是将服务器对象编码为可传输的有效载荷(JSON 文本或 Protobuf 二进制);反序列化 则是将这些字节解码回客户端/服务器的对象。
你选择的格式会影响带宽、延迟和用于编码/解码的 CPU 资源。
Protobuf 在网络传输上总是比 JSON 更小吗?
通常是。对于大对象、重复字段或深度嵌套的数据,Protobuf 使用数字标签和高效的二进制编码,往往比 JSON 更紧凑。
不过,如果启用 gzip 或 brotli,JSON 中重复字段名会被很好地压缩,因此在实际部署中两者的大小差距可能会缩小。建议同时比较原始和压缩后的大小。
在编码/解码和延迟方面,Protobuf 比 JSON 更快吗?
有可能。JSON 解析需要对文本进行分词、处理转义/Unicode,并将字符串转换为数字;Protobuf 解码更直接(读取标签 → 读取类型化值),因此常能减少 CPU 时间和内存分配。
但如果有效载荷非常小,整体延迟可能更多由 TLS、网络 RTT 及应用处理时间主导,而非序列化开销。
为什么 Protobuf 比 JSON 更难调试和记录日志?
默认情况下更难。JSON 是人可读的,便于在 DevTools、日志、curl 和 Postman 中直接查看。Protobuf 是二进制的,通常需要配套的 .proto 模式和解码工具。
常见的改进做法是同时记录一个解码后且已脱敏的调试视图(通常用 JSON 表示),并在日志中保留 request id 与关键字段。
JSON 与 Protobuf 在模式和类型安全上有何不同?
JSON 默认是“无模式”的,除非你强制使用 JSON Schema/OpenAPI。这种灵活性会导致字段不一致、“字符串化”数据(例如把数字或布尔当作字符串)、以及 null 语义不明确等问题。
Protobuf 通过 .proto 合约强制类型:数字就是数字,枚举受限于已知取值,时间戳常用约定类型表示。使用 Protobuf 时不同团队/语言之间保持一致性更容易。
在 JSON 和 Protobuf 中,如何在不破坏客户端的情况下演进 API?
Protobuf 的兼容性由**字段号(tag)**驱动。安全的变更通常是添加新的可选字段并使用全新的编号;破坏性变更包括重用旧字段号、以不兼容的类型替换字段、或移除字段而不保留编号。
最佳实践:对已移除的编号/名称使用 reserved,并保持变更日志。
JSON 则依赖惯例:尽量做增量变更,避免改变字段类型,必要时引入新字段名来替代旧字段。
API 能同时支持 JSON 和 Protobuf 吗?
可以。使用 HTTP 内容协商:
- 客户端发送
Accept: application/json或Accept: application/x-protobuf - 服务器返回相应的
Content-Type
同时设置 Vary: Accept,避免缓存层混淆不同格式的响应。如果工具链不方便协商,临时也可以提供单独的端点(例如 /v2/...)来过渡。
选择时应考虑哪些工具链和平台约束?
视环境而定:
- 浏览器/公共 API:JSON 几乎零成本且有更好的默认工具支持。\n- 移动/后端/内部服务:Protobuf 库成熟且通过代码生成带来明显好处。\n- gRPC 系统:Protobuf 与其生态紧密结合,支持生成存根、流式 RPC 等特性。
选择 Protobuf 时,还要考虑代码生成、CI 中的生成步骤和共享 .proto 版本管理的维护成本。
选择 Protobuf 而不是 JSON 会提升安全性或可靠性吗?
格式本身不是安全层。把 Protobuf 当成“更安全因为不可读”是一种误解。攻击者不需要人类可读的数据来发起攻击。
通用的防护措施对两种格式都适用:
- 限制最大请求/消息大小(包括解压后的大小)
- 使用超时与取消以防慢客户端或慢解析器消耗资源
- 严格校验业务规则(仅靠类型检查不足以保证语义正确)
- 在记录时脱敏敏感字段并保持解析库为最新以减少漏洞风险