我们在开发一些RPC调用的程序时,通常会涉及到对象的序列化/反序列化的问题,比如一个“Person”对象从Client端通过TCP方式发送到Server端;因为TCP协议(UDP等这种低级协议)只能发送字节流,所以需要应用层将Java对象序列化成字节流,数据接收端再反序列化成Java对象即可。“序列化”一定会涉及到编码(encoding,format),目前我们可选择的编码方式:
1)使用JSON,将java对象转换成JSON结构化字符串。在web应用、移动开发方面等,基于Http协议下,这是常用的,因为JSON的可读性较强。性能稍差。
2)基于XML,和JSON一样,数据在序列化成字节流之前,都转换成字符串。可读性强,性能差,异构系统、open api类型的应用中常用。
3)使用JAVA内置的编码和序列化机制,可移植性强,性能稍差。无法跨平台(语言)。
4)其他开源的序列化/反序列化框架,比如Apache Avro,Apache Thrift,这两个框架和Protobuf相比,性能非常接近,而且设计原理如出一辙;其中Avro在大数据存储(RPC数据交换,本地存储)时比较常用;Thrift的亮点在于内置了RPC机制,所以在开发一些RPC交互式应用时,Client和Server端的开发与部署都非常简单。
评价一个序列化框架的优缺点,大概有2个方面:1)结果数据大小,原则上说,序列化后的数据尺寸越小,传输效率越高。 2)结构复杂度,这会影响序列化/反序列化的效率,结构越复杂,越耗时。
Protobuf是一个高性能、易扩展的序列化框架,它的性能测试有关数据可以参看官方文档。通常在TCP Socket通讯(RPC调用)相关的应用中使用;它本身非常简单,易于开发,而且结合Netty框架可以非常便捷的实现一个RPC应用程序,同时Netty也为Protobuf解决了有关Socket通讯中“半包、粘包”等问题(反序列化时,字节成帧)。
1、安装Protobuf
从“https://developers.google.com/protocol-buffers/docs/downloads”下载安装包,windows下的使用不再赘言;在linux或者mac下,下载tar.gz的压缩包,解压后执行:
$ ./configure
$ make
$ make check
$ make install
此后,可以通过“protoc --version”查看是否安装成功了,安装过程不需要配置环境变量。安装主要是为了能够使用命令编译proto文件,实际部署环境并不需要。
2、样例
Protobuf需要一个schema声明文件,后缀为“.proto”的文本文件,内容样例如下:
option java_package = "com.test.protobuf";
option java_outer_classname="PersonProtos";
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phone = 4;
}
如果你曾经使用过thrift、avro,你会发现它们都需要一个类似的schema文件,只是结构规则不同罢了。特别备注:protbuf和thrift的声明文件相似度极高。
“message”表示,声明一个“类”,即java中的class。message中可以内嵌message,就像java的内部类一样。一个message有多个filed,“required string name = 1”则表示:name字段在序列化、反序列化时为第一个字段,string类型,“required”表示这个字段的值是必选;可以看出每个filed都至少有着三个部分组成,其中filed的“位置index”全局唯一。“optional”表示这个filed是可选的(允许为null)。“repeated”表示这个filed是一个集合(list)。也可以通过[default = ]为一个“optional”的filed指定默认值。
我们可以在一个.proto文件中声明多个“message”,不过大部分情况下我们把互相继承或者依赖的类写入一个.proto文件,将那些没有关联关系的类分别写入不同的文件,这样便于管理。
我们可以在.proto文件的头部声明一些额外的信息,比如“java_package”表示当“generate code”时将生成的java代码放入指定的package中。“java_outer_classname”表示生成的java类的名称。
然后执行如下命令,生成JAVA代码:
protoc --java_out=./ Persion.proto
通过“--java_out”指定生成JAVA代码保存的目录,后面紧跟“.proto”文件的路径。此后我们看到生成 了Package和一个PersonProto.java文件,我们只需要把此java文件复制到项目中即可。
3、JAVA实例
1)pom.xml
com.google.protobuf
protobuf-java
2.6.1
2)测试:
PersonProtos.Person.Builder personBuilder = PersonProtos.Person.newBuil