阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

Protocol Buffers的应用与分析

430次阅读
没有评论

共计 6804 个字符,预计需要花费 18 分钟才能阅读完成。

1  Protocol Buffers 的介绍

Protocol Buffers 是一种用于序列化结构化数据的机制,它具有灵活、高效、自动化的特点。类似于 XML,但是比 XML 更小巧、快捷、简单。在 Google 几乎所有它内部的 RPC 协议和文件格式都是采用 PB。
PB 具有以下特点:

  1. 平台无关、语言无关
  2. 高性能 比 XML 块 20-100 倍
  3. 体积小 比 XML 小 3 -10 倍
  4. 使用简单
  5. 兼容性好

在这里,我做了个小实验,将一个 29230KB 的自定义格式的文本数据转换成 PB 和 XML:

 PBXML
转换后的大小21011KB43202KB
解析时间(100 次循环)18610ms169251ms
完成解析所写代码行数 1 行50 行

与官方说法的差距,主要可能是因为应用场景不同,我的测试数据中字段比较长

表 1:PB 与 XML 的实验比较

可见,PB 作为一种轻量级的数据协议,在时间、空间上都有一定的优势。

2  Protocol Buffers 的简单应用

2.1  创建流程

2.1.1  定义一个.proto 文件

新建一个文件,命名为 addressbook.proto,内容如下:

package tutorial;// 命名空间
 
option java_package = "com.example.tutorial";// 生成文件的包名
option java_outer_classname = "AddressBookProtos";// 类名
 
message Person {// 要描述的结构化数据
 
    required string name = 1;//required 表示这个字段不能为空
    required int32 id = 2;// 等号后面的内容为数字别名
    optional string email = 3;//optional 表示可以为空
 
    PhoneNumber {// 内部 message
        required string number = 1;
        optional int32 type = 2;
    }
 
    repeated PhoneNumber phone = 4
}
 
message AddressBook {
    repeated Person person = 1;// 是个集合
}

对以上内容的一点解释:

  • PB 所支持的元类型数据请参考:PB 元类型数据
  • 修饰符 required:这个修饰符应该谨慎使用,滥用会导致后续的修改容易出现兼容性问题;
  • 修饰符 optional:对于常出现的属性,为节省空间应该取 1 -16 的别名;
  • PB 是以 key-value 的形式来将结构化数据序列化的。它采用了将等号后的数字别名以及属性的类型用 varints 编码成一个数字,来作为 key。

2.1.2  使用 PB 编译器

输入:protoc      -I=$SRC_DIR –java_out=$DST_DIR $SRC_DIR/addressbook.proto
其中    - I 指定.proto 文件所在目录
–java_out 指定生成 java 文件所在的目录

2.1.3  使用 PB 的 API 来写入和读取 messages

经过以上步骤,会在指定的 $DST_DIR 目录下生成一个 AddressBookProtos.java 的类。在 maven 中引入 protobuf-java 这个依赖后,利用这个类,便能序列化 / 反序列化数据了。
生成的代码结构如下:

class AddressBookProtos{
    class Person{
        class PhoneNumber{class Builder{}}
        class Builder{}
    }
    class AddressBook{class Builder{}}
} 

可以看到 Person、PhoneNumber、AddressBook 这些内部类则对应了所定义的那些 message。

2.2  序列化数据及分析

通过阅读代码可以看到,以上三个类的成员变量都是 private 类型的,并且,只提供了 getter 方法,而没有提供 setter 方法去为数据变量赋值。
PB 利用了内部类可以访问到外部类中私有成员变量的特性。对外部类的任何赋值操作都需要通过 Builder 内部类来进行。Builder 中有一个指向外部类的引用(名为 result),当赋值完成,调用 Builder 的 build()方法时,会把这个对象返回,同时使 result 指向 null。
PB 通过这样一种方式保证了数据安全性,一旦数据构建完毕,将无法再对其进行修改。
拿 PhoneNumber 这个类来说,对成员变量 number、type 赋值,需要以如下方式来进行:

PhoneNumber.Builder builder = PhoneNumber.newBuilder();
 
// 调用 setter 赋值,setter 返回了 this,所以可以链式表述
builder.setNumber("111").setType(1);
     
// 赋值完成后,调用 Builder 的 build 方法,将返回 PhoneNumber 对象
PhoneNumber phoneNumber = builder.build();

构建完成后,可以调用 writeTo 方法,将数据写入数据流中。

2.3  反序列化及分析

一行代码便能完成反序列化:

AddressBook  list = AddressBook .parseFrom(inputStream 或 buffer);

背后 PB 做了很多事情:

  1. 根据 inputStream 或者 buffer 去构造一个 CodedInputStream;
  2. 然后使用生成代码中的 mergeFrom 方法,去解析二进制数据:
    首先调用 CodedInputStream 的 readTag,也就是从中取得 key 值(int 类型),然后通过 swtich 块来往对象中赋值(PB 采用了 Base 128 Varints 的方式来编码这个数字,后面会介绍这种方式的)。
  3. 将数据解析完成后,会调用 build()方法,将构建好的对象返回。

 

更多详情见请继续阅读下一页的精彩内容:http://www.linuxidc.com/Linux/2014-09/107283p2.htm

3  message 的编码特点

PB 之所以解析速度快、所占体积小,很大程度上是由它序列化的编码特点来决定的。

3.1 Base 128 Varints

PB 采用了 Base 128 Varints 来变长编码整数:

  1. 变长编码的整数,它可能包含多个 byte,对于每个 byte 的 8 位,其中后 7 位表示数值,最高的一位表示是否还有还有另一个 byte,0 表示没有,1 表示有;
  2. 越前面的 byte 表示数值的低位,越后面的 byte 表示数值的高位;

例子:
300   varints  编码为:1010 1100 0000 0010
解释如下:
300 的 2 进制编码为:0001 0010 1100
按照刚才的规则,高低位颠倒,截取最后的 7 为放在第一个 byte,则第一 byte 为 1010 1100(其中最高位 1 表示,后续还有 byte);接着剩下的内容放到第二个 byte,为 0000 0010(其中最高位 0 表示,后续无 byte,这个数到这里截止了)。
于是,合在一起为 1010 1100 0000 0010;

3.2 Key-Value

如前所述,PB 的 message 是一系列的 key-value 对,在二进制数据中,使用 varints 数字(包含了别名以及属性类型信息)来作为 key,进而通过由 PB 编译器生成的代码来构造以及解析数据。
PB 将 key 编码成下面的结构:
X YYYY ZZZ
其中:最高位 X 表示是否还有后续的 byte 来编码数字别名;YYYY用于编码别名,定义了多余 16 个属性,则需要用到额外的 byte,所以出现频率高的字段应当取 1 -16 的别名);ZZZ表示这个字段的类型,PB 支持的属性的对应规则如下表:

TypeMeaningUsed For
0Varintint32, int64, uint32, uint64, sint32,sint64, bool, enum
164-bitfixed64, sfixed64, double
2Length-delimitedstring, bytes, embedded messages,packed repeated fields
3Start groupgroups (deprecated)
4End groupgroups (deprecated)
532-bitfixed32, sfixed32, floa

表 2:PB 属性对应规则
例子:
required int32 a=1;  在应用中给 a 赋值 150,序列化后 08 96 01

  • 08 代表的是 key 0 0001 000,最高位为0,表示这个 key 为一个 byte,中间四位表示 a 的数字别名,最后三位表示 a 的属性类型;
  • 96 01 代表的是 value,二进制为:1001 0110 0000 0001
    → 001 0110    000 0001(去掉最高位)
    → 22              +  1*2^7 = 150

3.3 Zig-Zag

采用 varints 的方式编码有符号的整数,效率比较差,因为负数的最高位是 1,这样就导致了情况类似于编码一个很大的数。

为了解决这个问题,Protocol Buffers 定义了 sint32/sint64 属性,他们采用了“之字形”(ZigZag)编码的方式,将负数编码成正数,交替进行。看了下表就很好理解了:

Signed OriginalEncoded As
00
-11
12
-23
21474836474294967294
21474836484294967295

表 3:Zig-Zag 编码规则
利用这个方式,可以有效地节省存储空间,也能提高解析效率。

了解了以上内容,对于其他数据类型的编码,也是很好理解的,大家可以参考官方文档,这里不做详述。

4 其他

官方文档中,有提到 PB 提供了 RPC 的接口,但是没有提供具体实现。当在的.proto 文件中,加入如下定义:

1
2
3
service XXX {
    rpc MMM(request) returns(response);
}

PB 便会为你生成一个代表这个服务的 XXX 虚类,通过实现这个类中的 abstract MMM 方法,以及提供 RpcChannel 的实现,你便可以利用 Protocol Buffers 实现你的 RPC 了。

第三方的 RPC 实现大家可以参考 ThirdPartyRPC

在这里,我利用了第三方实现 protobuf-socket-rpc,写了一个小例子,有兴趣的可以看看。如下:Protocol buffer 的 rpc 例子

5 小结

PB 具有跨平台、解析速度快、序列化数据体积小、扩展性高、使用简单的特点。但是我们也可以看到,相比于 XML,PB 的数据,并不是自然可读的;同时它生成的代码不是纯 pojo,对于代码有一定的侵入性。在你的项目中,如果对于以上缺点要求并不高,可以尝试着使用 PB。

Protocol Buffers 的详细介绍:请点这里
Protocol Buffers 的下载地址:请点这里

1  Protocol Buffers 的介绍

Protocol Buffers 是一种用于序列化结构化数据的机制,它具有灵活、高效、自动化的特点。类似于 XML,但是比 XML 更小巧、快捷、简单。在 Google 几乎所有它内部的 RPC 协议和文件格式都是采用 PB。
PB 具有以下特点:

  1. 平台无关、语言无关
  2. 高性能 比 XML 块 20-100 倍
  3. 体积小 比 XML 小 3 -10 倍
  4. 使用简单
  5. 兼容性好

在这里,我做了个小实验,将一个 29230KB 的自定义格式的文本数据转换成 PB 和 XML:

 PBXML
转换后的大小21011KB43202KB
解析时间(100 次循环)18610ms169251ms
完成解析所写代码行数 1 行50 行

与官方说法的差距,主要可能是因为应用场景不同,我的测试数据中字段比较长

表 1:PB 与 XML 的实验比较

可见,PB 作为一种轻量级的数据协议,在时间、空间上都有一定的优势。

2  Protocol Buffers 的简单应用

2.1  创建流程

2.1.1  定义一个.proto 文件

新建一个文件,命名为 addressbook.proto,内容如下:

package tutorial;// 命名空间
 
option java_package = "com.example.tutorial";// 生成文件的包名
option java_outer_classname = "AddressBookProtos";// 类名
 
message Person {// 要描述的结构化数据
 
    required string name = 1;//required 表示这个字段不能为空
    required int32 id = 2;// 等号后面的内容为数字别名
    optional string email = 3;//optional 表示可以为空
 
    PhoneNumber {// 内部 message
        required string number = 1;
        optional int32 type = 2;
    }
 
    repeated PhoneNumber phone = 4
}
 
message AddressBook {
    repeated Person person = 1;// 是个集合
}

对以上内容的一点解释:

  • PB 所支持的元类型数据请参考:PB 元类型数据
  • 修饰符 required:这个修饰符应该谨慎使用,滥用会导致后续的修改容易出现兼容性问题;
  • 修饰符 optional:对于常出现的属性,为节省空间应该取 1 -16 的别名;
  • PB 是以 key-value 的形式来将结构化数据序列化的。它采用了将等号后的数字别名以及属性的类型用 varints 编码成一个数字,来作为 key。

2.1.2  使用 PB 编译器

输入:protoc      -I=$SRC_DIR –java_out=$DST_DIR $SRC_DIR/addressbook.proto
其中    - I 指定.proto 文件所在目录
–java_out 指定生成 java 文件所在的目录

2.1.3  使用 PB 的 API 来写入和读取 messages

经过以上步骤,会在指定的 $DST_DIR 目录下生成一个 AddressBookProtos.java 的类。在 maven 中引入 protobuf-java 这个依赖后,利用这个类,便能序列化 / 反序列化数据了。
生成的代码结构如下:

class AddressBookProtos{
    class Person{
        class PhoneNumber{class Builder{}}
        class Builder{}
    }
    class AddressBook{class Builder{}}
} 

可以看到 Person、PhoneNumber、AddressBook 这些内部类则对应了所定义的那些 message。

2.2  序列化数据及分析

通过阅读代码可以看到,以上三个类的成员变量都是 private 类型的,并且,只提供了 getter 方法,而没有提供 setter 方法去为数据变量赋值。
PB 利用了内部类可以访问到外部类中私有成员变量的特性。对外部类的任何赋值操作都需要通过 Builder 内部类来进行。Builder 中有一个指向外部类的引用(名为 result),当赋值完成,调用 Builder 的 build()方法时,会把这个对象返回,同时使 result 指向 null。
PB 通过这样一种方式保证了数据安全性,一旦数据构建完毕,将无法再对其进行修改。
拿 PhoneNumber 这个类来说,对成员变量 number、type 赋值,需要以如下方式来进行:

PhoneNumber.Builder builder = PhoneNumber.newBuilder();
 
// 调用 setter 赋值,setter 返回了 this,所以可以链式表述
builder.setNumber("111").setType(1);
     
// 赋值完成后,调用 Builder 的 build 方法,将返回 PhoneNumber 对象
PhoneNumber phoneNumber = builder.build();

构建完成后,可以调用 writeTo 方法,将数据写入数据流中。

2.3  反序列化及分析

一行代码便能完成反序列化:

AddressBook  list = AddressBook .parseFrom(inputStream 或 buffer);

背后 PB 做了很多事情:

  1. 根据 inputStream 或者 buffer 去构造一个 CodedInputStream;
  2. 然后使用生成代码中的 mergeFrom 方法,去解析二进制数据:
    首先调用 CodedInputStream 的 readTag,也就是从中取得 key 值(int 类型),然后通过 swtich 块来往对象中赋值(PB 采用了 Base 128 Varints 的方式来编码这个数字,后面会介绍这种方式的)。
  3. 将数据解析完成后,会调用 build()方法,将构建好的对象返回。

 

更多详情见请继续阅读下一页的精彩内容:http://www.linuxidc.com/Linux/2014-09/107283p2.htm

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-20发表,共计6804字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中

星哥玩云

星哥玩云
星哥玩云
分享互联网知识
用户数
4
文章数
19350
评论数
4
阅读量
7963592
文章搜索
热门文章
星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛 NAS-6:抖音视频同步工具,视频下载自动下载保存 前言 各位玩 NAS 的朋友好,我是星哥!...
星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛 NAS-3:安装飞牛 NAS 后的很有必要的操作 前言 如果你已经有了飞牛 NAS 系统,之前...
我把用了20年的360安全卫士卸载了

我把用了20年的360安全卫士卸载了

我把用了 20 年的 360 安全卫士卸载了 是的,正如标题你看到的。 原因 偷摸安装自家的软件 莫名其妙安装...
再见zabbix!轻量级自建服务器监控神器在Linux 的完整部署指南

再见zabbix!轻量级自建服务器监控神器在Linux 的完整部署指南

再见 zabbix!轻量级自建服务器监控神器在 Linux 的完整部署指南 在日常运维中,服务器监控是绕不开的...
飞牛NAS中安装Navidrome音乐文件中文标签乱码问题解决、安装FntermX终端

飞牛NAS中安装Navidrome音乐文件中文标签乱码问题解决、安装FntermX终端

飞牛 NAS 中安装 Navidrome 音乐文件中文标签乱码问题解决、安装 FntermX 终端 问题背景 ...
阿里云CDN
阿里云CDN-提高用户访问的响应速度和成功率
随机文章
12.2K Star 爆火!开源免费的 FileConverter:右键一键搞定音视频 / 图片 / 文档转换,告别多工具切换

12.2K Star 爆火!开源免费的 FileConverter:右键一键搞定音视频 / 图片 / 文档转换,告别多工具切换

12.2K Star 爆火!开源免费的 FileConverter:右键一键搞定音视频 / 图片 / 文档转换...
零成本上线!用 Hugging Face免费服务器+Docker 快速部署HertzBeat 监控平台

零成本上线!用 Hugging Face免费服务器+Docker 快速部署HertzBeat 监控平台

零成本上线!用 Hugging Face 免费服务器 +Docker 快速部署 HertzBeat 监控平台 ...
三大开源投屏神器横评:QtScrcpy、scrcpy、escrcpy 谁才是跨平台控制 Android 的最优解?

三大开源投屏神器横评:QtScrcpy、scrcpy、escrcpy 谁才是跨平台控制 Android 的最优解?

  三大开源投屏神器横评:QtScrcpy、scrcpy、escrcpy 谁才是跨平台控制 Andr...
还在找免费服务器?无广告免费主机,新手也能轻松上手!

还在找免费服务器?无广告免费主机,新手也能轻松上手!

还在找免费服务器?无广告免费主机,新手也能轻松上手! 前言 对于个人开发者、建站新手或是想搭建测试站点的从业者...
每天一个好玩的网站-手机博物馆-CHAZ 3D Experience

每天一个好玩的网站-手机博物馆-CHAZ 3D Experience

每天一个好玩的网站 - 手机博物馆 -CHAZ 3D Experience 一句话介绍:一个用 3D 方式重温...

免费图片视频管理工具让灵感库告别混乱

一言一句话
-「
手气不错
零成本上线!用 Hugging Face免费服务器+Docker 快速部署HertzBeat 监控平台

零成本上线!用 Hugging Face免费服务器+Docker 快速部署HertzBeat 监控平台

零成本上线!用 Hugging Face 免费服务器 +Docker 快速部署 HertzBeat 监控平台 ...
星哥带你玩飞牛NAS-16:飞牛云NAS换桌面,fndesk图标管理神器上线!

星哥带你玩飞牛NAS-16:飞牛云NAS换桌面,fndesk图标管理神器上线!

  星哥带你玩飞牛 NAS-16:飞牛云 NAS 换桌面,fndesk 图标管理神器上线! 引言 哈...
每年0.99刀,拿下你的第一个顶级域名,详细注册使用

每年0.99刀,拿下你的第一个顶级域名,详细注册使用

每年 0.99 刀,拿下你的第一个顶级域名,详细注册使用 前言 作为长期折腾云服务、域名建站的老玩家,星哥一直...
你的云服务器到底有多强?宝塔跑分告诉你

你的云服务器到底有多强?宝塔跑分告诉你

你的云服务器到底有多强?宝塔跑分告诉你 为什么要用宝塔跑分? 宝塔跑分其实就是对 CPU、内存、磁盘、IO 做...
240 元左右!五盘位 NAS主机,7 代U硬解4K稳如狗,拓展性碾压同价位

240 元左右!五盘位 NAS主机,7 代U硬解4K稳如狗,拓展性碾压同价位

  240 元左右!五盘位 NAS 主机,7 代 U 硬解 4K 稳如狗,拓展性碾压同价位 在 NA...