概述

protobuf(也叫做pb)是谷歌开源的一款序列化、反序列化的框架,由于其出色的效率、高效的编码格式使得 数据在序列化、反序列化以及传输的过程中有很大的优势,因此在rpc数据交换或者数据存储中有很好的应用。

  • 优点:

    • 高效:这里的高效是指其在序列化、反序列化的过程中占用的时间特别少,究其原因是因为其只在字节流和对象之间进行转换,相较于其他的序列化、反序列化的框架如json,还要把对象组织称用户易于读懂的格式
    • 节省带宽:由于其高效的编码方式,使得序列化之后的数据占用的空间特别小
  • 缺点:

    • 不易读懂:鉴于其高效的编码格式,使得数据在序列化或者反序列化之后很难读懂,也正因为如此,我们基本上没有在涉及前段开发的过程中使用pb这种协议来传输数据

示例

笔者所用的语言是java,因此演示示例也适用java来演示。官网上有演示如何使用pb来定义java对象以及如何使用,不过很遗憾, 这种演示方式在真正构建大型工程的时候很难,因为我们总不能把pb文件一个一个的编译,然后考到指定的目录,这有点不切实际, 大型的项目更多的是会使用maven的插件来进行构建整个工程,如下演示如何使用maven插件来构建,工程的整体结构如下图所示: 如下为maven需要的依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.4.0</version>
</dependency>
</dependencies>

<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.4.1.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:3.1.0:exe:${os.detected.classifier}
</protocArtifact>
<pluginId>grpc-java</pluginId>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

接下来我们使用protobuf3来编写一个模型

1
2
3
4
5
6
7
8
9
10
syntax = "proto3";

option java_package = "com.h3c";
option java_outer_classname = "PersonBuilder";

message Person {
int32 id = 1;
string name = 2;
string email = 3;
}

然后我们使用pb的插件来生成对应的Java类,生成的java类最终会在target目录下,不过我们的工程是可以引用到的,如下:

这里需要注意,pom文件中的protoc版本最好和本机安装的保持一直,这样可以直接使用mvn clean install命令来生成对应的类,因为我自己在使用的时候发现 会报错:

1
protoc-gen-grpc-java: program not found or is not executable

这是因为网上的一些教程通常会指定:

1
<goal>compile-custom</goal>

选项,将这个选项去掉后就可以正常的打包了。 最后我们就可以在工程中引用这些类,测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class App {
public static void main(String[] args) throws InvalidProtocolBufferException {
PersonBuilder.Person.Builder builder = PersonBuilder.Person.newBuilder();
PersonBuilder.Person person = builder.setId(1)
.setName("wes")
.setEmail("wes@gmail.com").build();
byte[] pb = person.toByteArray();

for (byte b : pb) {
System.out.println(b);
}

PersonBuilder.Person p1 = PersonBuilder.Person.parseFrom(pb);
System.out.println(p1);
}
}

对应的测试结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
8
1
18
3
119
101
115
26
13
119
101
115
64
103
109
97
105
108
46
99
111
109
id: 1
name: "wes"
email: "wes@gmail.com"

总结

相较于json这种序列化仅仅包含字符串和数字类型,pb的类型则要丰富的多(不过pb的模型应该是不具备继承特性的,后续补充),具体可以参考以下文章:

[pb终极教程] https://colobu.com/2019/10/03/protobuf-ultimate-tutorial-in-go/