当前位置: 首页 > 后端技术 > Java

ProtocolBuffers,比xml快100倍的序列化框架

时间:2023-04-02 00:06:57 Java

我们平时习惯的数据存储格式有Json、XML等形式,但相信很多人都没有听说过ProtocolBuffer(简称protobuf).protobuf是谷歌开源的一种独立于语言和平台的通信协议。其体积小、效率高和友好的兼容性设计使其得到广泛应用。性能比Json和XML强多了!而且,随着微服务架构的流行,RPC框架也成为了服务框架的重要组成部分。在很多RPC设计中,都会用到高性能的codec技术,protobuf就是其中的佼佼者。也就是说,需要学习protobuf的使用和原理,才能深入理解微服务架构中RPC链路的底层实现,设计高效的传输、序列化、编解码功能。protobuf简介protobuf是一个序列化对象框架(或编解码器框架)。它由两个功能组成:结构化数据(数据存储结构)和序列化&反序列化。数据存储结构的作用类似于XML和JSON;序列化和反序列化的作用类似于Java自带的序列化,Facebook的Thrift和JBossMarshalling等。简而言之:protobuf就是通过定义结构化数据,提供序列化和反序列化功能,实现数据存储/RPC数据交换的功能数据。其特点是:语言无关,平台无关,简单高性能(序列化速度快&序列化后数据量小),兼容性好,可以通过数据直观的看到不同框架之间序列化响应时间的对比:可以看出protobuf的性能比其他框架要高很多。protobuf的使用过程上面已经介绍了protobuf的功能,但是仅仅知道这些功能我们并不能知道它是如何使用的。网上看了一大堆文章,要么直接开始写代码,要么直接开始分析报文格式,新手往往一头雾水。那么,我们先梳理一下protobuf的使用步骤。上图中,protobuf的使用分为四步:第一步,搭建环境:使用protobuf定义通信的数据结构,编译生成不同的编程语言代码,需要这样的编译环境。第二步,构建数据:使用protobuf传输数据,那么数据包含什么,有哪些项,整个结构是什么样子的。这里,数据结构是根据protobuf的语法定义的。第三步,项目集成:集成pom依赖(以Java为例),集成编译好的Java类(相对于proto文件);step4,具体使用:使用集成的Java类构建消息,赋值,然后根据protobufsequencereceiver进行反序列化操作;了解了以上步骤后,下面对具体步骤进行实战演示。这里的演示是基于MacOS操作系统和Java编程语言。如果你使用的是其他操作系统和编程语言,基本思路是一样的,具体操作可以在不同的步骤中找到。安装ProtocolBuffers,安装protobuf,定义数据结构,生成相应的编程语言代码。通常有两种方式:本地安装和IDE插件。我们先看看本地安装。protobuf的代码托管在GitHub上,对应地址为:https://github.com/protocolbu...。点击项目右侧的发布链接可以看到对应的版本:https://github.com/protocolbu...。各种编程语言和环境的版本都包含在这里。本文选择protobuf-java-3.17.3.zip版本。Mac操作系统下,编译安装protobuf前需要先安装依赖组件。安装依赖组件://安装ProtocolBuffer依赖//注:ProtocolBuffer依赖autoconf、automake、libtool、curlbrewinstallautoconfautomakelibtoolcurlunzipprotobuf-java-3.17.3.zip,进入根目录,执行以下命令command://运行autogen.sh脚本。/autogen.sh//运行configure.sh脚本。/configure//编译未编译的依赖包make//检查依赖包是否完整makecheck//开始安装ProtocolBuffermakeinstall安装完成后查看Version:$protoc--versionlibprotoc3.14.0输出版本信息,说明安装成功。这里的protoc命令是ProtocolBuffer的编译器,可以将.proto文件编译成对应平台的头文件和源代码文件。另一种方法是安装IDE插件。这里我们以IDEA为例,搜索插件:关于protobuf的插件有很多,选择适合自己的就可以了。那么gRPC官方推荐了一个更优雅的使用姿势,通过maven即可轻松搞定(需要安装上图中的“ProtobufSupport”插件)。即引入grpc的一些组件,然后在maven构建中进行配置,将proto文件编译成Java代码。这个方法暂时不展开,以后可以直接看项目集成部分的源码。构建数据在Java中,如果我们要通过JSON传输一段数据,首先需要定义一个对象,这里以Person为例:publicclassPerson{privateStringname;私有整数id;//...getter/setter}那么,如果使用protobuf来定义Person对象的数据结构是怎样的呢?首先创建一个person.proto文件,然后定义如下结构:syntax="proto3";//声明为protobuf3定义文件打包教程;选项java_package="com.choupangxia.protobuf.message";//声明生成消息类java包路径选项java_outer_classname="Person";//声明生成消息的类名classmessagePersonProto{stringname=1;int32id=2;}上面各个语法的具体描述可以参考评论区。当然Person的结构可以更丰富。这里只是为了演示目的的最简单的例子。更多语法请参考官方文档。编译完protot文件的定义后,我们可以通过两种方式生成目标Java类。这里我们首先使用本机安装的编译器进行操作。在执行protoc命令之前,可以先执行-h命令查看protoc的使用说明:protoc-h进入person.proto文件所在目录,执行如下命令编译:protoc--java_out=。./java./person.proto--java_out参数指定Java类的输出路径,第二个参数要编译执行的文件为当前目录下的person.proto文件。执行命令,会发现在com.choupangxia.protobuf.message下生成了一个名为Person的类。注意proto中定义的消息名不能和Java类名重名,否则命令执行失败。对应的Person类比较复杂,甚至有一些语法错误或改进。如果有必要,相应的改进和优化就足够了。上图展示了生成的Person类的部分结构。比如上面的java.lang.StringgetName()方法的返回值可以在不指定String包的情况下进行优化。项目集成其实就是将上面提到的生成的Person代码放到项目中,这已经是项目集成的一部分了。如果不引入protobuf依赖,上面的代码还是会报错。在Maven项目的pom文件中添加protobuf依赖:com.google.protobufprotobuf-java3.17.3如果想直接通过IDEA编译proto文件,需要安装“ProtobufSupport”插件,需要引入grpc依赖。完整的依赖如下:1.6.13.17.3com.google.protobufprotobuf-java${protobuf.version}/version>io.grpcgrpc-netty${grpc.version}提供io.grpcgrpc-protobuf${grpc.version}提供io.grpcgrpc-stub${grpc.version}提供<扩展名><扩展名>kr.motd.mavenos-maven-plugin1.5.0.Finalorg.xolstice.maven.pluginsprotobuf-maven-plugin0.5.0<配置>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}grpc-javaio.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}<执行><执行>compilecompile-custom执行mavencompile之前命令编译完成后,将需要编译的proto文件放在与src/main/java同级目录下的/src/main/proto目录下。这个时候只要将生成的java复制到对应的包中即可。业务应用一切准备就绪,下面我们来写一个例子使用相应的代码。公共类测试{publicstaticvoidmain(String[]args)throwsInvalidProtocolBufferException{Person.PersonProtosourcePersonProto=Person.PersonProto.newBuilder().setId(123).setName("Tom").build();//序列化byte[]binaryInfo=sourcePersonProto.toByteArray();System.out.println("序列化字节码内容:"+Arrays.toString(binaryInfo));System.out.println("序列化字节码长度:"+binaryInfo.length);System.out.println("------------下面是接收端的反序列化操作------------");//反序列化Person.PersonPrototargetPersonProto=Person.PersonProto.parseFrom(binaryInfo);System.out.println("反序列化结果:"+targetPersonProto.toString());}}上面的代码是基于生成Person类的基础使用。首先通过Person类中的内部类和Builder方法封装参数,然后调用其toByteArray方法,将消息信息序列化。接收端有同一套代码,先获取Person.PersonProto对象,然后执行parseFrom方法进行反序列化操作。为什么protobuf效率更高,从序列化数据量的角度来分析。与XML、JSON等文本协议相比,ProtoBuf采用T-(L)-V(TAG-LENGTH-VALUE)模式编码,不需要"、{、}、:等定界符来构造信息。同时在编码层面使用了varint压缩,所以描述同样的信息,protobuf的序列化体积要小很多,在网络中传输消耗的网络流量也少。此外,对于网络资源紧张、性能要求非常高的场景,ProtoBuf协议是一个不错的选择。举个简单直观的例子:{"id":1,"firstName":"Chris","lastName":"Richardson","email":[{"type":"PROFESSIONAL","email":"aichrrdson@email.com"}]}对于上面的JSON数据,使用JSON序列化后的数据大小为118bytes,而使用protobuf序列化后的数据大小为48bytes。如果数据量更大,层次结构更复杂,差距还是很明显的。从序列化/反序列化速度来看,protobuf序列化/反序列化与XML和JSON相比,速度更快,比XML快20-100倍。但是protobuf是基于二进制的协议,编码后的数据可读性差。如果没有idl文件,则无法理解二进制数据流,对调试也不友好。小结本文带你从0到1学习protobuf的使用步骤,很多文章之所以看不懂,是因为没有梳理整个使用protobuf的核心逻辑。只要掌握如何搭建环境,如何编写数据结构,如何编译,如何集成到项目中并使用就可以了。然后可以在实践中逐步补充protobuf的其他知识点。随着微服务的不断发展,为了在RPC框架中追求高效的通信,不可避免地要使用protobuf这样的框架。也是更好学习微服务架构底层的必备知识。本文源码:https://github.com/secbr/prot...博主简介:《SpringBoot技术内幕》技术书籍作者,热爱研究技术,写干货技术文章。公众号:《程序新视野》,博主的公众号,欢迎关注~技术交流:请联系博主微信号:zhuan2quan