从 JSON 到 Protobuf,深入序列化方案的选型与原理
序列化:数据跨越边界的翻译官
序列化(Serialization)用于描述 RPC 服务接口和数据结构。在 RPC 通信中,客户端和服务器之间传输的数据通常是结构化的,如调用方法、请求参数、返回值等。这些结构化数据需要通过序列化过程转换为二进制流,以便在网络中进行传输。目前,常见的跨语言序列化编码方式包括 XML、JSON 和 Protobuf。尽管 XML 曾经广泛使用,但现在已经逐渐被淘汰。JSON 目前正处于其使用高峰,而 Protobuf 则是一种新兴并且正在快速发展的序列化方式。值得一提的是,gRPC 默认选择使用 Protobuf 作为其序列化方式。
JSON
JSON(JavaScript Object Notation)是一种轻量级的文本数据格式,以其优秀的可读性、灵活性和跨语言兼容性而广受欢迎。由于其结构简单、规范明确,JSON 在 Web 开发、移动应用、API 通信等领域得到了广泛应用。同时,JSON 还可以与其他技术和工具集成,如 RESTful API、NoSQL 数据库等,进一步扩展了其应用范围。
假设用 UTF-8 编码,每个字符占用 1 个字节。估算上面 JSON 占有的内存数据。1)字段名占用的内存空间: int (3 字节)+ str (3 字节)+ str (4 字节)= 10 字节。2)字段值占用的内存空间:12345 (5 字节)+ hello (5 字节)+ true (4 字节)= 14 字节。需注意 JSON 中数值和布尔类型会被编码为文本字符串。3)分隔符和其他符号占用的内存空间::(3 字节)+ ,(2 字节)+ {}(2 字节)+ "(8 字节)= 15 字节 JSON 的内存占用为:10 + 14 + 15 = 39 个字节,其中有效的字段值只占 14 个字节。 可见 JSON 的内存占有比较大且效率低,这个问题的主要有如下原因。1)非字符串编码低效:int 字段值,转成 JSON 要五个字节。 bool 字段值占了四个字节。2)字段名信息冗余:同一个对像,只是字段值不同,每次都要传输相同的字段名。
Protobuf
Protobuf(Protocol Buffers)是由 Google 开发的一种高效的二进制序列化格式。它设计精巧,旨在提供一种简单、动态、可扩展且性能高效的数据序列化方案。相比于 XML 和 JSON 等其他序列化编码方式,Protobuf 具有更小的数据体积和更快的数据解析速度,这使得它在处理大量数据和高性能需求的场景中具有显著优势。
Varint
Varint 是一种变长的整数类型,相较于定长的编码方式,更能节省空间。Varint 使用每个字节的最高位(Most Significant Bit,MSB),记录字节读取是否结束。 如果 MSB 为 1 ,表示还有后序字节,一直读到 MSB 为 0 的字节为止。一个 int32 整型通常占据 4 个字节也就是 32 位,但使用 Varint 编码只需 1 个字节。
wire type
Protobuf 将每个字段编码后从逻辑上分为三个部分。
其中 tag 里面会包含两部分信息:字段序号(field number),字段类型(wire type)。tag,type 和 length 都用 VarInts 表示。Protobuf 在 3 版本中定义了 4 种类型 。
由于 3 和 4 表示的类型已经废弃,类型比较少,所以 Protobuf 在编码时候只用了 3 bit,实际传输以 (tag<<3)|type 的方式传输。

使用 tag 的优点是不用重复传输字段名,但也因为没有字段名,所以须维护字段名和 tag 的映射关系。这个映射关系由.proto 维护 。将 message 通过 Protobuf 序列化的二进制串,与原始字段名和字段值有如下的对应关系。

Protobuf 在多个方面都展现出与 JSON 相比的优势。首先,Protobuf 的数据更为紧凑,相较于 JSON 的文本格式,它可以大幅减少数据的存储和传输开销。其次,Protobuf 的处理速度更快,由于采用了二进制编码,它能够更快地将二进制数据转换为内存对象。此外,Protobuf 还提供了类型安全的保障,通过预先定义消息结构,确保数据的一致性和正确性。然而,与 JSON 相比,Protobuf 由于采用了二进制编码,Protobuf 的数据在可读性方面稍逊一筹。此外,Protobuf 需要预先定义消息结构,这增加了一些额外的工作量,并且在消息结构发生变化时,需要同步进行更新。需要明确的是,序列化并非 RPC 协议本身,而是将 RPC 传输的结构化数据(如请求参数、返回值)序列化成二进制流的过程。因此,RPC 协议中需要包含序列化标识,以便接收端根据序列化标识将二进制流反序列化成结构化数据。然而,像 HTTP/1 协议直接将文本数据转换成二进制流,因此不需要额外的序列化标识。序列化的性能直接影响到 RPC 协议的性能。一个优秀的序列化编码方式应该在占用更低的内存空间的同时,保持更高的编解码效率。除了 JSON 和 Protobuf 之外,还有一些特定语言的序列化编码方式,如 Java 的 Hessian、Kryo 等,它们在特定的场景中也可以作为优秀的选择。
总结:没有银弹,只有最合适的选择
构建高效、健壮的服务通信体系,其核心在于制定一套能够有效协调跨服务、跨边界协作的规范与机制。在复杂的异构系统交互中,必须系统性地解决数据格式的统一性、信息传输的高效性以及方法定义的明确性这三大基础问题。标准化框架(如 gRPC): 它们通过整合 HTTP/2 的流式交互能力和 ProtoBuf 的统一编解码方案,构建了一个功能完备、开箱即用且具有广泛生态支持的开放 RPC 体系。这尤其适用于需要跨语言、跨团队协作以及面临复杂多变公网环境的场景。精简的自研协议: 它们更聚焦于榨取内网环境下的极致性能潜力。通过高度定制、可扩展的报文结构设计和灵活的过程控制,自研协议能够针对特定业务场景和硬件环境进行深度优化,满足对低延迟、高吞吐的严苛要求。这种“公网标准化”与“内网优化”并存的双轨实践,深刻体现了系统设计中的一个根本逻辑:在标准化带来的互操作性、生态繁荣与特定场景下的极致优化之间,寻求一种动态的、弹性的平衡。 技术决策的目标应是既能有力支撑当前业务的快速发展,又能为未来通信潜能的持续释放奠定坚实基础。
很高兴与你相遇!如果你喜欢本文内容,记得关注哦!
版权声明: 本文为 InfoQ 作者【poemyang】的原创文章。
原文链接:【http://xie.infoq.cn/article/5498e788037bd1b9b50deea0c】。文章转载请联系作者。
评论