Skip to content

protobuf 协议原理

一、Protobuf 是什么?

Protobuf 是 Google 开发的一种跨平台、语言无关、可扩展的序列化协议。它类似于 XML/JSON,但更小、更快、更简单。

二、核心原理

1. 结构化数据定义

使用 .proto 文件定义数据结构:

protobuf
message Person {
  required string name = 1;
  optional int32 id = 2;
  repeated string email = 3;
}

2. 编码原理 - 紧凑的二进制格式

Key 原理:Tag-Length-Value (TLV) 变种

[field_number + wire_type] [length] [value]

Wire Types 类型:

  • 0:Varint(变长整数)
  • 1:64-bit(固定长度)
  • 2:Length-delimited(长度分隔)
  • 3/4:Start/End group(已废弃)
  • 5:32-bit(固定长度)

3. 关键技术细节

a) Varint 编码 - 变长整数

  • 每个字节最高位是延续位(1=还有后续,0=结束)
  • 小数值用更少字节存储
  • 示例:300 编码为 1010 1100 0000 0010

b) ZigZag 编码

  • 处理有符号负数的效率问题
  • 公式:(n << 1) ^ (n >> 31)(32 位)

c) 字段编号代替字段名

  • 字段编号(1, 2, 3...)代替字段名
  • 极大减少传输数据量

d) 可选/必选/重复字段

  • required:必须存在(v3 已移除)
  • optional:可选字段
  • repeated:数组/列表

三、工作流程

.proto 文件定义

protoc 编译器生成代码

应用使用生成的类

序列化为二进制

网络传输/存储

反序列化为对象

四、与其它序列化对比

特性ProtobufJSONXML
大小很小(二进制)很大
速度非常快很慢
可读性差(二进制)
模式演进优秀有限
语言支持广泛广泛广泛

五、核心优势

1. 高性能

  • 比 JSON 快 3-10 倍
  • 体积小 3-10 倍
  • 高效的编解码算法

2. 向后/向前兼容

  • 字段编号机制允许:
    • 新增字段不影响旧客户端
    • 删除字段(设为 deprecated)
    • 字段重命名(编号不变即可)

3. 强类型和代码生成

  • 编译时类型检查
  • 自动生成序列化代码
  • IDE 支持(自动补全等)

4. 跨语言互操作性

  • 一份 .proto,多语言使用
  • 保证不同语言间数据一致性

六、主要应用场景

1. gRPC 通信协议

  • Protobuf 是 gRPC 的默认 IDL
  • 高性能微服务间通信

2. 配置文件

  • 相比 XML/JSON 更高效
  • 例如:Kubernetes 配置

3. 数据存储

  • 高效持久化存储格式
  • 数据库记录序列化

4. 网络协议

  • 自定义 TCP/UDP 协议
  • 游戏开发、IoT 设备通信

5. 分布式系统

  • 服务间数据交换
  • 消息队列消息格式

七、最佳实践

1. 编号策略

protobuf
message User {
  // 1-15: 常用字段(1字节)
  int32 id = 1;        // 常用
  string name = 2;     // 常用

  // 16+: 非常用字段(2字节+)
  string bio = 16;     // 不常用
  map<string, string> metadata = 17;
}

2. 版本兼容设计

  • 不要修改现有字段编号
  • 新字段用新的编号
  • 废弃字段添加 deprecated 标记

3. 数据类型选择

  • 小整数用 int32 而非 int64
  • 小数用 floatdouble
  • 枚举节省空间

八、实际示例

定义:

protobuf
syntax = "proto3";

message Order {
  int64 order_id = 1;
  string user_id = 2;
  repeated OrderItem items = 3;
  float total = 4;
  OrderStatus status = 5;
}

message OrderItem {
  string product_id = 1;
  int32 quantity = 2;
  float price = 3;
}

enum OrderStatus {
  PENDING = 0;
  PAID = 1;
  SHIPPED = 2;
  DELIVERED = 3;
}

使用:

python
# 序列化
order = Order(
    order_id=12345,
    user_id="user_001",
    items=[OrderItem(product_id="p1", quantity=2)],
    total=99.99,
    status=OrderStatus.PAID
)
data = order.SerializeToString()  # 很小的二进制数据

# 反序列化
new_order = Order()
new_order.ParseFromString(data)

九、限制和注意事项

缺点:

  1. 二进制不可读:需要专门工具解析
  2. 需要预定义模式:不如 JSON 灵活
  3. 内存消耗:解析时需要完整加载(流式处理有限)

适用场景判断:

  • 适合:性能敏感、内部服务通信、固定模式数据
  • 不适合:需要人工编辑的配置、对外公开 API(RESTful)、简单临时数据存储

十、现代生态

Protobuf v3 改进:

  • 移除 required 关键字
  • 默认值处理更明确
  • 支持 JSON 映射
  • AnyOneof 类型

相关工具:

  • protoc:编译器
  • protoc-gen-go:Go 代码生成
  • buf:现代 Protobuf 工具链
  • 各种语言的运行时库

总结

Protobuf 的核心价值在于在强类型约束下提供极致性能的序列化方案。它在微服务、分布式系统、性能敏感场景中是不可或缺的技术选择。通过字段编号机制和紧凑的二进制编码,在保证向前/向后兼容性的同时,大幅提升了数据处理效率。

选择 Protobuf 的关键考虑因素是:性能需求 > 人类可读性需求,以及是否需要跨语言的一致性强类型保证