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

Protocol Buffers的应用与分析

120次阅读
没有评论

共计 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、短信等云产品特惠热卖中