一文解析 Protobuf 好在哪里。

一、什么是protobuf:数据序列化的一种方式

Protocol Buffers(通常缩写为 protobuf),它是 Google 开发的一种高效、跨平台的结构化数据序列化机制。

1.1 Protobuf的优点

它主要有四个好处:

1,空间小+速度快。空间小,指的是序列化后二进制数据的大小,和json、xml相比,占用的空间更小。速度快,指的是序列化、反序列化的速度,和json、xml相比更快。(和protobuf的二进制编码方式有关)

2,跨语言性好。使用 .proto 文件定义数据结构后,可以使用官方或社区提供的编译器 (protoc) 生成多种编程语言(如 C++, C#, Go, Java, Python,等)的源代码。这解决了不同语言编写的服务之间交换数据的难题,是构建异构系统(尤其是微服务架构)的理想选择。

3,清晰的接口定义和强类型。使用 .proto 文件定义数据结构后,生成的代码是强类型的,编译器(语言编译器或 IDE)能在开发阶段就捕获许多类型错误(例如赋值错误类型、访问不存在的字段),提高了代码的健壮性和可维护性。

4,优秀的向后/向前兼容性旧代码可以读取新格式的数据, 新代码也可以读取旧格式的数据(见第四节)

1.2 与竞品的横向比较

protobuf 和 xml、json 横向对比

二、protubuf 为什么那么好?

Protobuf 之所以具有 1.1 中的四个优点,主要是因为两个东西,一是 TLV + 二进制编码二是 .proto 文件。

2.1 TLV + 二进制编码

2.11 TLV

TLV指的是数据传输的格式。

T:Tag。字段的唯一标识。 标识数据的类型。要用Varin编码。

Tag = (字段编号 << 3) | Wire Type。例如字段编号=2,Wire Type=2 =》 (2<<3)|2 = 18,

后面再用Varin编码, 18 =》 0x12

L:Length。仅在 Wire Type=2 时存在。要用Varin编码。

当 Value 部分的长度是可变且无法从类型本身推断时(如Wire Type=2),需要明确指定其长度。

后面再用Varin编码,如长度是 5 =》 Varint 编码后为 0x05 (1 字节)。

V:Value。该字段的实际值。编码方式,由 wire_type 决定。

Protobuf 定义了 6 种wire_type :

  • 0 (Varint): 用于 int32, int64, uint32, uint64, sint32, sint64, bool, enum。用 Varint 编码。
  • 1 (64-bit): 用于 fixed64, sfixed64, double。固定 8 字节。固定长度编码(Fixed32/Fixed64),8 字节定长。
  • 2 (Length-delimited): 用于 string, bytes, 嵌套消息 (messages), packed repeated fields。Value 前有 Length长度分隔类型(Length-Delimited)。
  • 3 (Start group): 已废弃。
  • 4 (End group): 已废弃。
  • 5 (32-bit): 用于 fixed32, sfixed32, float。固定 4 字节。固定长度编码(Fixed32/Fixed64),4 字节定长。

2.12 常见的几种编码

1, base 128 Varints (针对≤28bit的正整数)
  • 核心思想:
  • 编码规则:
  • 示例 (编码数字 300):
  • 优缺点与适用场景:
2,ZigZag 编码(针对负整数的)
  • 问题: 负整数(int32, int64)如果直接用二进制补码表示,再用 Varint 编码,结果通常会很大(因为最高位是 1),占用很多字节,效率低下。
  • 解决方案: sint32sint64 类型使用 ZigZag 编码
  • 原理: 将有符号整数映射到无符号整数空间,使得小的负数和小的正数都能映射成小的无符号整数,从而可以用紧凑的 Varint 编码。
  • 转换公式:
  • 映射效果:
  • 优势: 无论正负,绝对值小的数字都能被编码成小的无符号整数,从而用很短的 Varint 表示。例如,-1 被映射成 1,只需一个字节 (0x01) 编码。而直接用补码的 int32 -1 (0xFFFFFFFF) 用 Varint 编码需要 5 个字节!
  • 补充:Varints 编码的实质在于设法移除数字开头的 0 ⽐特, ⽽对于负数, 由于其数字⾼位都是 1, 因此 Varints 编码在此场景下失效, Zigzag 编码便是为了解决这个问题, Zigzag 编码的⼤致思想是⾸先对负数做⼀次变换, 将其映射为⼀个正数, 变换以后便可以使⽤ Varints 编码进⾏压缩, 这⾥关键的⼀点在于变换的算法, ⾸先算法必须是可逆的, 即可以根据变换后 的值计算出原始值, 否则就⽆法解码, 同时要求变换算法要尽可能简单, 以避免影响 Protobuf 编码、 解码的速度。
3,固定长度编码(Fixed32/Fixed64)
  • 定长存储:无论数值大小,固定占用 4 字节(fixed32/sfixed32/float)或 8 字节(fixed64/sfixed64/double)。
  • 适用场景
4,长度分隔类型编码(Length-Delimited)
  • TLV 结构
  • 示例:字符串 "hello"(UTF-8 编码,长度 5)
  • 适用场景:

2.13 举例讲解 TLV+编码

message Person {
  int32 id = 1;       // 字段编号=1, Wire Type=0 
  string name = 2;    //字段编号=2, Wire Type=2
}

假设,id=42, name="Alice"。

id 字段的 TLV:

T (Tag): (1 << 3) | 0 = 8 -> Varint 编码后为 0x08 (1 字节)。0x08 是十六进制。

L (Length): 不存在 (因为 Wire Type 0 是固定含义,不需要显式长度)。

V (Value): 42 -> Varint 编码后为 0x2A (1 字节)。0x2A 是十六进制。

完整 TLV: 08 2A (2 字节)

name 字段的 TLV:

T (Tag): (2 << 3) | 2 = 16 | 2 = 18 -> Varint 编码后为 0x12 (1 字节)。

L (Length): "Alice" 的 UTF-8 字节长度是 5 -> Varint 编码后为 0x05 (1 字节)。

V (Value): "Alice" 的 UTF-8 字节: 0x41 (A), 0x6C (l), 0x69 (i), 0x63 (c), 0x65 (e)。

完整 TLV: 12 05 41 6C 69 63 65 (7 字节)

整个消息的二进制流: 08 2A 12 05 41 6C 69 63 65

2.2 .proto 文件

.proto 文件的好处。见1.1.

2,跨语言性好。使用 .proto 文件定义数据结构后,可以使用官方或社区提供的编译器 (protoc) 生成多种编程语言(如 C++, C#, Go, Java, Python,等)的源代码。这解决了不同语言编写的服务之间交换数据的难题,是构建异构系统(尤其是微服务架构)的理想选择。

3,清晰的接口定义和强类型。使用 .proto 文件定义数据结构后,生成的代码是强类型的,编译器(语言编译器或 IDE)能在开发阶段就捕获许多类型错误(例如赋值错误类型、访问不存在的字段),提高了代码的健壮性和可维护性。

4,优秀的向后/向前兼容性。旧代码可以读取新格式的数据, 新代码也可以读取旧格式的数据(见第四节)

三、接收方如何解析字段?—— 没有字段名如何工作

如题,我们知道protobuf传输数据的时候,是不传字段名的,那接受方怎么知道接受的是谁呢。

核心原理:字段编号 + 预共享Schema(.proto文件)

发送前:双方需完全相同的.proto文件(如 user.proto)

message User {
  int32 id = 1;      // 字段编号1 → int32类型
  string name = 2;   // 字段编号2 → string类型
}

接收方解析流程:

为什么不需要字段名?

字段编号是终极标识符。

.proto 中字段编号和类型绑定,接收方通过编号直接定位字段定义(如编号2=string类型的name)

Wire Type提供物理存储方案

告诉解析器如何读取数据(如跳过未知字段时:Wire Type=2 → 先读长度再跳过对应字节)

Schema即协议

.proto 文件是通信双方的权威数据字典,字段名只在生成代码时有用,传输时完全被数字编号替代。

四、之前定义的 message 新增字段,会怎么样

实战示例

原始 .proto文件

message User {
  int32 id = 1;
  string name = 2;
}

.proto文件 新增字段

message User {
  int32 id = 1;
  string name = 2;
  // 安全新增字段 ↓
  optional string email = 3;   
  repeated string tags = 4;   
}

新旧交互结果

#百度##字节##腾讯##阿里##美团#
全部评论

相关推荐

无面如何呢:无性别对立的意思,但是努力3年被集美一句话搞没了工作我是真绷不住了,今天上班看到给我无语地笑了2小时,一想到以后我要是工作对接时因为用词不当被辞退我就想笑
点赞 评论 收藏
分享
评论
1
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务