乐趣区

关于protobuf:Protocol-Buffers一款比xml快100倍的序列化框架

咱们通常习惯用 Json、XML 等模式的数据存储格局,但置信还有很多人没有据说过 Protocol Buffer(简称 protobuf)。protobuf 是 Google 开源的一个语言无关、平台无关的通信协议,其玲珑、高效和敌对的兼容性设计,使其被宽泛应用。性能比 Json、XML 真的强太多了!

而且,随着微服务架构的风行,RPC 框架也成为服务框架的重要组成部分。在很多 RPC 的设计中,都采纳了高性能的编解码技术,而 protobuf 就属于其中的佼佼者。

也就说,要想深刻理解微服务架构中的 RPC 环节底层实现,设计出高效的传输、序列化、编码解码等性能,学习 protobuf 的应用和原理十分有必要。

protobuf 简介

protobuf 是一种序列化对象框架(或者说是编解码框架)。它有两局部性能组成:结构化数据(数据存储构造)和序列化 & 反序列化。

其中数据存储构造的作用与 XML、JSON 类似;序列化和反序列化的作用与 Java 自带的序列化、Facebook 的 Thrift 和 JBoss Marshalling 等类似。

总之:protobuf 是通过定义结构化数据,并提供对数据的序列化和反序列化性能,从而实现数据存储 /RPC 数据交换的性能。

它的特点是:

  • 语言无关、平台无关
  • 简洁
  • 高性能(序列化速度快 & 序列化后的数据体积小)
  • 良好的兼容性

能够通过数据直观的看一下不同框架在序列化响应工夫上的比照:

能够看出,protobuf 的性能要远高于其余框架。

protobuf 的应用流程

下面介绍了 protobuf 的性能,但仅仅晓得这些性能咱们无奈晓得它是怎么应用的。看了网上很多的文章,要么间接开始写代码要么间接开始剖析报文格式,对于老手来说往往会一头雾水。

所以,咱们先来梳理一下应用 protobuf 的步骤。

在上图中将 protobuf 的应用分了四个步骤:

  • 步骤一,搭建环境:应用 protobuf 要定义通信的数据结构,并编译生成不同的编程语言代码,这就须要有这么一个编译器的环境。
  • 步骤二,构建数据:应用 protobuf 是要传输数据的,那么数据蕴含什么,有哪些项目,整个构造档次是什么样子的。这里基于 protobuf 的语法来进行数据结构的定义。
  • 步骤三,我的项目集成:集成 pom 依赖(Java 为例)、集成编译的 Java 类(对照 proto 文件);
  • 步骤四,具体应用:通过集成进来的 Java 类,来构建音讯、赋值,而后基于 protobuf 进行序列化,接管方进行反序列化操作;

理解了上述步骤,上面就针对具体的步骤来进行实战演示。

这里演示基于 Mac OS 操作系统和 Java 编程语言来进行操作。如果你应用的是其余操作系统和编程语言,基本思路一样,在不同的步骤时可针对性的找一下具体操作。

装置 Protocol Buffers

装置 protobuf 是为了进行数据结构的定义和对应编程语言代码的生成。通常有两种形式:本地装置和 IDE 插件。咱们先来看本地装置。

protobuf 的代码是托管在 GitHub 上的,对应地址为:https://github.com/protocolbu…。

点击我的项目左边的 release 链接可看到对应版本:https://github.com/protocolbu…。

这里蕴含了各种编程语言、环境的版本。本文选 protobuf-java-3.17.3.zip 版本。

在 Mac 操作系统下,须要先装置一下依赖组件,才可能对 protobuf 进行编译和装置。

装置依赖组件:

// 装置 Protocol Buffer 依赖
// 注:Protocol Buffer 依赖于 autoconf、automake、libtool、curl
brew install autoconf automake libtool curl

解压 protobuf-java-3.17.3.zip,进入根目录,执行以下命令:

// 运行 autogen.sh 脚本
./autogen.sh

// 运行 configure.sh 脚本
./configure
 
// 编译未编译的依赖包
make
 
// 查看依赖包是否残缺
make check
 
// 开始装置 Protocol Buffer
make install

装置实现,测验版本:

$protoc --version
libprotoc 3.14.0

输入版本信息,阐明装置胜利。

这里的 protoc 命令就是 Protocol Buffer 的编译器,能够将 .proto 文件编译成对应平台的头文件和源代码文件。

另外一种形式就是装置 IDE 插件,这里以 IDEA 为例,搜寻插件:

对于 protobuf 的插件比拟多,抉择适宜本人就行。

而后 gRPC 官网举荐了一种更优雅的应用姿态,能够通过 maven 轻松搞定(需装置上图中的“Protobuf Support”插件)。也就是引入 grpc 的一些组件,而后在 maven 的 build 中进行配置,编译 proto 文件成为 Java 代码。此种形式临时不开展,后续可间接看我的项目集成局部的源代码。

构建数据

在 Java 中,如果通过 JSON 来传输一个数据,咱们首先要定义一个对象,这里以 Person 为例:

public class Person {
    private String name;
    private Integer id;
    // ... getter/setter
}

那么,如果用 protobuf 来定义 Person 这个对象的数据结构是什么样呢?

先创立一个 person.proto 文件,而后定义如下的构造:

syntax = "proto3"; // 申明为 protobuf 3 定义文件
package tutorial;

option java_package = "com.choupangxia.protobuf.message"; // 申明生成音讯类的 java 包门路
option java_outer_classname = "Person";  // 申明生成音讯类的类名

message PersonProto {
    string name = 1;
    int32 id = 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 中定义的 message 名称不要与 Java 类名反复,否则会呈现命令执行失败的情况。

对应的 Person 类比较复杂,甚至有一些语法层面的谬误或改良,如果须要,进行对应的改良优化即可。

上图为生成的 Person 类的局部构造。比方下面的 java.lang.String getName() 这个办法的返回值就能够进行优化,不必指定 String 的 package。

我的项目集成

其实下面讲生成的 Person 代码放入我的项目,曾经算是我的项目集成的一部分了。如果未引入 protobuf 的依赖,下面的代码还是会报错的。

Maven 我的项目的 pom 文件中增加 protobuf 依赖:

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.17.3</version>
</dependency>

如果想通过 IDEA 间接编译 proto 文件,需装置“Protobuf Support”插件,还需引入 grpc 的依赖,残缺依赖如下:

<properties>
    <grpc.version>1.6.1</grpc.version>
    <protobuf.version>3.17.3</protobuf.version>
</properties>

<dependencies>
    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java</artifactId>
        <version>${protobuf.version}</version>
    </dependency>
    <!-- 编译应用局部 -->
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-netty</artifactId>
        <version>${grpc.version}</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-protobuf</artifactId>
        <version>${grpc.version}</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-stub</artifactId>
        <version>${grpc.version}</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
<build>
    <extensions>
        <extension>
            <groupId>kr.motd.maven</groupId>
            <artifactId>os-maven-plugin</artifactId>
            <version>1.5.0.Final</version>
        </extension>
    </extensions>
    <plugins>
        <plugin>
            <groupId>org.xolstice.maven.plugins</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>
            <version>0.5.0</version>
            <configuration>
                <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
                <pluginId>grpc-java</pluginId>
                <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>compile-custom</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

在执行执行 maven compile 命令进行编译之前,将须要编译的 proto 文件放在与 src/main/java 同级目录下的 /src/main/proto 目录。

此时将生成的 Java 复制到对应的包下即可。

业务利用

所有准备就绪,当初就来写个例子应用对应的代码了。

public class Test {public static void main(String[] args) throws InvalidProtocolBufferException {Person.PersonProto sourcePersonProto = 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.PersonProto targetPersonProto = 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":"aicchrrdson@email.com"}]}

对于下面的 JSON 数据,应用 JSON 序列化后的数据大小为 118byte,而应用 protobuf 序列化后的数据大小为 48byte。如果数据量更多,层次结构更简单,差距还是很显著的。

从序列化 / 反序列化速度角度,与 XML、JSON 相比,protobuf 序列化 / 反序列化的速度更快,比 XML 要快 20-100 倍。

但 protobuf 是基于二进制的协定,编码后的数据可读性差,如果没有 idl 文件,就无奈了解二进制数据流,对调试不敌对。

小结

本文带大家从 0 到 1 学习了 protobuf 的应用步骤。很多文章之所以看不懂,就是因为没有梳理分明应用 protobuf 的整个外围逻辑。只有把握了如何搭建环境、如何编写数据结构、如何编译、如何集成到我的项目中并使用。那么,protobuf 的其余知识点逐渐在实践中补充即可。

随着微服务的一直倒退,RPC 框架为了谋求高效的通信,应用像 protobuf 这类框架也必然是趋势。也是想更好的学习微服务架构的底层的必备常识。

本文源码:https://github.com/secbr/prot…

博主简介:《SpringBoot 技术底细》技术图书作者,热爱钻研技术,写技术干货文章。

公众号:「程序新视界」,博主的公众号,欢送关注~

技术交换:请分割博主微信号:zhuan2quan

退出移动版