Protobuf

 11th May 2022 at 6:51pm

Protobuf 是 Google 出品的结构化数据格式。特点是 语言中立平台中立。下面的内容基于 pb3 在 2020 年 10 月的版本。

流程

  1. 写一个结构定义文件
  2. 用 protoc 将其生成为编程语言相应的代码
  3. 配合 protobuf 各语言的 runtime 就可以使用

上手指南

  1. 根据 指引 安装 protoc 以及你要使用的语言相应的 runtime
  2. 根据 language guide 理解一个 .proto 文件的基础写法,写好后用 protoc 生成代码文件
  3. 找到你对应语言的 generated code 文档,了解生成的代码文件 API 如何使用

Language Guide

这份 cheat sheet 简要地描述了 proto 文件的语言规则。

pb3 去掉了 required / optional 标识(在 pb2 中存在),一切都是 optional。

数值类型根据值的特点会有不同的编解码性能。比如对于负数,int32 的编解码性能比 sint32 差。根据数值的分布情况来选择数值类型。

对于没打包的字段,解包时会默认用其类型的 默认值,比如数值类型是 0,字符串类型是空字符串。由于这个默认值规则,因此 enum 类型的第一个值必须是 0。

enum 类型支持 aliasSTARTEDRUNNING 值都为 1),但需要显式打开选项:

enum EnumAllowingAlias {
  option allow_alias = true;
  UNKNOWN = 0;
  STARTED = 1;
  RUNNING = 1;
}

可以为 Message 或者 enum 类型保留某些 tag number 或是字段名,使其不能被使用

enum Foo {
  reserved 2, 15, 9 to 11, 40 to max;
  reserved "FOO", "BAR";
}

字段类型一般是不能修改的(有兼容性问题),但一些 情况 例外(主要是数值类型)。

可以引入别的 proto 文件中的类型

// 该文件定义了自己的 package:google.protobuf
import "google/protobuf/any.proto";

// import public 表示,当其他 proto 文件引用本文件时,other.proto 中的类型都会暴露出去
import public "other.proto";

message Message {
  // 引用 import 进来的消息类型时,需要用它的全称
  repeated google.protobuf.Any details = 1;
}

使用 Any 类型时,可以将任意一个 message 类型的值打包进 Any 字段。

Oneof 表示 message 里面,某些字段只能有一个字段被设置上值。

Protobuf 的结构可以 跟 JSON 互相转换

Message 不支持继承

Well-Known Types

Google 提供了一些常用的类,称之为 well-known types,也用 proto 文件表达。使用时需要在 proto 文件中 import 进来:

import "google/protobuf/timestamp.proto";

message Person {
  string name = 1;
  // ...
  google.protobuf.Timestamp last_updated = 5;
}

但我觉得里面没多少好用的东西

Best Practices

团队应该维护一个公共的基础 message 库,比如定义业务相关的基础数据类型(比如用户),通用的结构(比如时间)等。

Uber 在 pb 上耕耘了很久,prototoolbuf 非常值得一看(我还没看)。

这份 文档 提供了 PB 相关的编程语言库、工具、RPC 实现等。