protobuf一种适合传输的序列化方案
protobuf一种适合传输的序列化方案
前言
protobuf是Google提出的序列化方案,此方案独立于语言和平台,目前提供了如c++,go,python等多种语言的实现,使用比较广泛,具有性能开销小,压缩率高等特点
protobuf有V2和V3两个主要差异很大的版本。下面会介绍两种版本的特点
proto2
这个版本在编写 .proto
文件时的字段有三种限定符,分别是required
、optional
和 repeated
。
- required:必须设置该字段,如果是在debug模式下编译 libprotobuf,则序列化一个未初始化(未对required字段赋值)的 message 将导致断言失败。在release模式的构建中,将跳过检查并始终写入消息,但解析未初始化的消息将返回false表示失败。
- optional:可以设置也可以不设置该字段。如果未设置可选字段值,则使用默认值,也可以用[default = value]进行设置。
- repeated:该字段可以重复任意次数(包括零次),可以将 repeated 字段视为动态大小的数组。
message定义
定义一个简单的 message 结构如下:
1 | message Person { |
观察 message 定义可以看到每个字段后面都有 = 1
、= 2
的标记,这些被称为 Tags,在 protobuf 中同一个 message 中的每个字段都需要有独一无二的tag,tag 为 1-15 的是单字节编码,16-2047 使用2字节编码,所以1-15应该给频繁使用的字段。
关于tag的取值,还有一种范围是[1,536870911]的说法,同时 19000 到 19999 之间的数字也不能使用,因为它们是 protobuf 的实现中保留的,也就是 FieldDescriptor::kFirstReservedNumber
到 FieldDescriptor::kLastReservedNumber
指定的范围,如果使用其中的数字,导出 .proto 文件时会报错,此处存疑,需要验证一下。
message扩展
在使用的了 protobuf 的项目发布以后,绝对会遇到扩展原有 message 结构的需求,这一点不可避免,除非发布后的项目不再升级维护了,要想扩展就需要兼容之前的代码逻辑,这里有一些必须遵守的规则,否则就达不到兼容的目的。
- 不能更改任何现有字段的 tag
- 不能添加或删除任何 required 字段
- 可以删除 optional 或 repeated 的字段
- 可以添加新的 optional 或 repeated 字段,但必须使用新的tag,曾经使用过又删除的 tag 也不能再使用了
注意事项
proto2中对required的使用永远都应该非常小心。如果想在某个时刻停止写入或发送required字段,直接将字段更改为可选字段将会有问题。一些工程师得出的经验是,使用required弊大于利,它们更喜欢只使用optional和repeated。
proto3
proto3比proto2支持更多语言但更简洁,去掉了一些复杂的语法和特性。
- 在第一行非空白非注释行,必须写:syntax = “proto3”;
- 直接从语法层面上移除了 required 规则,取消了 required 限定词
- 增加了对 Go、Ruby、JavaNano 等语言的支持
- 移除了 default 选项,字段的默认值只能根据字段类型由系统决定
一个完整的例子
先来看一个非常简单的例子。假设你想定义一个“搜索请求”的消息格式,每一个请求含有一个查询字符串、你感兴趣的查询结果所在的页数,以及每一页多少条查询结果。可以采用如下的方式来定义消息类型的.proto文件了:
1 | syntax = "proto3"; |
文件的第一行指定了你正在使用proto3语法:如果你没有指定这个,编译器会使用proto2。这个指定语法行必须是文件的非空非注释的第一个行。
SearchRequest消息格式有3个字段,在消息中承载的数据分别对应于每一个字段。其中每个字段都有一个名字和一种类型。
接下来我们看一个整体的例子吧,以下是demo的结构
第一步,编写addressbook.proto文件,注意以.proto结尾
1 | syntax = "proto3"; |
- 第二步,编译addressbook.proto文件
1 | zhiliaodeMBP:protobuf_python zhiliao$ protoc ./addressbook.proto --python_out=./ |
编译完毕,会自动生成addressbook_pb2.py文件
另外protoc ./addressbook.proto –python_out=./这是输入的命令行指令,意思是在当前目录输出默认的即可,也即是截图里面的addressbook_pb2.py
第三步,编译.py文件,进行序列化和凡序列化
add_person.py
1 | #! /usr/bin/env python |
编译该py文件,输出结果如下:
1 | b'\n6\n\x05safly\x10\x01\x1a\x0csafly@qq.com%\n\x07zD(\x012\x08\n\x06123456:\x0c\n\x04\x08\x01\x10\x01\n\x04\x08\x02\x10\x02' <class 'bytes'> |
我们就看到了序列化和反序列化的结果