Protocol Buffers(常简称 Protobuf)是一种语言无关、平台无关的结构化数据序列化机制,用 .proto schema 定义消息结构,再生成不同语言的读写代码,把结构化对象编码成稳定的 Wire Format

核心问题

程序内部的数据结构不能直接跨语言、跨进程或跨机器传输。JSON 容易读,但类型弱、字段名重复、体积较大;直接传内存结构又会受语言运行时、字节序、对齐和版本影响。Protocol Buffers 解决的是:用一份 schema 约定字段编号和类型,让多语言程序都能用同一种二进制格式读写同一类消息。

核心对象

对象作用工程关注点
.proto 文件schema 源文件package、message、service、import
Message一种结构化记录类型字段编号、字段类型、嵌套结构
Field number字段在 wire format 中的稳定编号一旦发布不能随便复用
Field type字段值类型int、string、bool、message、repeated 等
Cardinality字段出现次数语义singular、optional、repeated
Generated codeprotoc 生成的语言绑定builder、getter、parser、serializer
Wire format实际写入网络或文件的字节布局varint、length-delimited、未知字段

核心机制

一个最小 .proto 可以写成:

message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;
}

这里 name = 11 不是默认值,而是字段编号。Protobuf 的二进制编码主要靠字段编号和 wire type 识别字段,而不是靠字段名。因此改字段名通常不破坏已发布数据,但复用或改错字段编号会破坏兼容性。

编码时,每个字段大致变成:

field key = (field_number << 3) | wire_type
field bytes = encode(field key) + encode(value)
  • field_number.proto 里写的编号,例如 123
  • wire_type 表示底层编码类别,例如 varint、fixed32、fixed64、length-delimited。
  • << 3 是按位左移 3 位,给低 3 位留出 wire type 的位置。
  • | 是按位或,把字段编号和 wire type 合成一个 key。
  • encode(value) 按字段类型把值编码成字节,例如整数常用 varint,字符串常用长度前缀加 UTF-8 字节。

读消息时,解析器不需要字段名。它读到 field key 后拆出字段编号和 wire type,再根据当前 schema 把值填入生成代码里的对象。如果遇到未知字段,较新的实现通常可以跳过或保留,从而支持“新发送方给旧接收方多发字段”的演进模式。

工程用途

  • 定义跨语言通信协议,例如 gRPC service 和 OTLP schema。
  • 保存结构化二进制文件或队列消息。
  • 用稳定 schema 支撑接口演进、测试向量和代码生成。
  • 降低手写解析器的错误率,尤其是多语言 SDK 和后端服务之间。

观察 protobuf 使用质量时,重点看字段编号是否稳定、未知字段策略、消息大小、解析失败率、schema 版本、生成代码版本和跨语言测试覆盖。

边界与常见坑

  • Protobuf 不是自描述格式:只拿到字节通常不能完整理解含义,接收方还需要对应 .proto schema。
  • 不要复用已删除字段编号:旧数据或旧客户端可能仍使用该编号,应使用 reserved 保留。
  • 字段名不是 wire-level 身份:二进制格式核心依赖字段编号,改名和改编号的风险完全不同。
  • 默认值和 presence 要小心:特别是 proto3 中,字段“没出现”和“出现但等于默认值”的语义可能影响业务判断。
  • 不是压缩或加密方案:protobuf 比很多文本格式更紧凑,但不等于压缩;也不提供保密性或完整性。
  • 不适合所有大数组场景:大量浮点矩阵、图像或科学计算数据可能需要专门格式,否则解析和内存开销不一定理想。

相关术语

外部参考