Protocol Buffers 是 Google 开发一种数据描述语言,能够将结构化数据序列化,可用于数据存储、通信协议等方面。据 Google 官方文档介绍,现在 Google 内部已经有 48,162 个消息类型定义在 12,183 个 proto 文件中。本文会从快速入门、语言规范、编码协议、性能评估等几个方面对 Prototol Buffers 进行介绍。
不了解 Protocol Buffers 的同学可以把它理解为更快、更简单、更小的 JSON 或者 XML,区别在于 Protocol Buffers 是二进制格式,而 JSON 和 XML 是文本格式。
相对于 XML,Protocol Buffers 的具有如下几个优点:
- 简洁
- 体积小:消息大小只需要 XML 的 1/10 ~ 1/3
- 速度快:解析速度比 XML 快 20 ~ 100 倍
- 使用 Protocol Buffers 的编译器,可以生成更容易在编程中使用的数据访问代码
- 更好的兼容性,Protocol Buffers 设计的一个原则就是要能够很好的支持向下或向上兼容。
看一个简单的对比例子,表达一个用户的三个基本的属性,如果使用 XML 消息体大小为 82 bytes。
如果使用 JSON 消息体大小为 56 bytes。
使用 Protocol Buffers 咋则只需要 31 bytes,看到这些二进制数据大家可以暂时忽略,后面会具体分析这些二进制数据是如何编码的。
接下来先看一个简单的入门示例,在该例子中我们从准备环境开始,编写 proto 文件,到最后使用 Protocol Buffers 编译器生成代码,再到具体的使用。
从 https://github.com/google/protobuf 下载编译安装 protoc,并下载 ProtobufSDK。
开始编写 proto 文件,使用 message 关键字定义消息类型,消息中每个字段需要指定字段类型和字段序号。同一个 message 中字段
使用 protoc 命令生成代码,使用--cpp_out、--java_out、--python_out 命令选项可以生成 C++、Java、Python 代码,在最新版本 Protocol Buffers v3 中还加入了 ruby 语言的支持。
生成代码的代码可以直接加入到自己的代码工程中使用,以 C++ 语言为例:
这是一段 Java 语言的使用示例:
接下来会详细说明如何定义 proto 文件:
在消息定义中,我们需要确定三个问题:
-
确定消息命名,给消息取一个有意义的名字。
-
指定字段的类型
-
定义字段的编号,在 Protocol Buffers 中,字段的编号非常重要,字段名仅仅是作为参考和生成代码用。需要注意的是字段的编号区间范围,其中 19000 ~ 19999 被 Protocol Buffers 作为保留字段。
字段约束,required 指定该字段必须赋值,禁止为空(在 v3 中该约束被移除);optional 指定字段为可选字段,可以为空,对于 optional 字段还可以使用[default]指定默认值,如果没有指定,则会使用字段类型的默认值;使用 repeated 指定字段为集合。
在一个 proto 文件中可以同时定义多个 message 类型,生成代码时根据生成代码的目标语言不同,处理的方式不太一样,如 Java 会针对每个 message 类型生成一个.java 文件。还可以使用 C++ 风格的注释。
在 Protocol Buffers 中提供了很多的标量类型,供我们在定义字段类型时使用。
可以指定字段的类型为其他 message 类型,如图中的示例代码所示:
还可以使用 import 关键字导入其他 proto 文件,这有利于你进行自己的 proto 文件的规划和整理。
在 proto 文件中消息的类型还可以嵌套,如你定义的 message 类型仅作为另外一个 Message 的字段类型。
为了便于扩展,在 proto 文件中可以使用 extensions 关键字预留一部分字段编号出来,以便于后期给第三方扩展时使用。
oneof 关键字指定一组字段中,至少要有一个字段必须赋值。如在用户登录系统中,使用邮箱和用户名都可以登录该系统,所以通常会要求至少提供用户名或者邮箱。
在这一部分总我们会仔细分析,Protocol Buffers 序列化后的二进制代码的编码协议,不知道这些并不会影响我们使用 Protocol Buffers,但是了解之后有助于我们更好的使用 Protocol Buffers 和进行调试。
先从一个简单的例子开始,如图中的代码所示,我们有这样一个消息定义,在使用中给 a 赋值为 150,最终编码得到的结果是
08 96 01
,为什么编码的结果是这样,其中 08 又代表什么?后续一一为你介绍。在 Protocol Buffers 中采用 Base-128 变长编码,所谓变长编码是和定长编码相对的,定长编码使用固定字节数来表示,如 int32 类型的数字固定使用 4 bytes 表示,而变长编码是需要几个字节就使用几个字节,如对于 int32 类型的数字 1 来说,只需要 1 bytes 足够。Base-128 变长编码的原则就两条:
-
每个字节使用使用低 7 位表示数字,除了最后一个字节,其他字节的最高位都设置为 1。
-
采用 Little-Endian 字节序
一个 Protocol Buffers 的消息包含一系列字段 key/value,每个字段由一个变长 32 位整数作为字段头,后面跟随字段体。字段头的格式如下:
(field_number << 3) | wire_type
-field_number: 字段序号
-wire_type: 字段编码类型
这里是详细的字段说明,其中 3、4 已经放弃:
接下来我们对 Protocol Buffers 的性能做一些测试。
在测试过程中,我们使用一个统一的消息体格式,主要评估以下两个性能指标:
- 序列化速度
- 报文大小
尽管 Protocol Buffers 有序列化速度快、报文体积小以及更好的兼容性等优点,但同时也有一些缺点,在使用时要根据实际情况来选择使用。
- 缺乏自描述,可读性差,可以使用 TextFormat
- 适用于内部服务和存储,而不适合直接对外公开,如 Open API,protobuf v3 将加入对 json 的支持,可解决此问题
与 Protocol Buffers 类似的框架有微软出的 Bond 和 Facebook 出的 Thrift,感兴趣的同学可以去下载研究一下。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于