目录
- 前言
- 特点
- 语法定义
- 关键字
- JSON与Protocol Buffers互相转换
- gRPC与Protocol Buffers的关系
前言
Protocol Buffers(通常简称为protobuf)是Google公司开发的一种数据描述语言,它能够将结构化数据序列化,可用于数据存储、通信协议等方面。这种序列化格式很灵活、高效、自动化,不依赖于语言和平台并且可扩展性极强。使用protobuf时,您只需将数据结构定义一次(使用.proto文件定义),便可以使用特别生成的源代码轻松地使用不同的数据流完成对这些结构数据的读写操作,即使使用不同的语言(protobuf的跨语言支持特性)。您甚至可以更新数据结构的定义(就是更新.proto文件内容),而不会破坏依赖“老”格式编译出来的程序。
特点
Protocol Buffers主要具有以下三大特点:
- 语言无关 :支持多种语言,包括但不限于Java、Python、C++、JavaScript、Go、Ruby、PHP、Objective-C、C#等。
- 平台无关 :可生成不同语言的代码并在任何环境中运行。
- 性能好、扩展性好 :序列化和反序列化性能相比 JSON 和 XML 等更快,平均每秒可以处理 10 万条消息。在微服务场景中,使用合适的序列化协议会大大提高系统的性能。
语法定义
Protocol Buffers(Protobuf)的语法主要包括以下几个部分:
- 消息定义:在
.proto
文件中定义消息,消息由字段组成。字段有三种类型:required
、optional
、repeated
,分别表示必须、可选和重复。
message Person {
required string name = 1;
optional int32 id = 2;
repeated string email = 3;
}
- 枚举定义:枚举类型允许你定义一组有限的可能的值。
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
- 服务定义:服务允许你定义一组相互关联的RPC(远程过程调用)。
service HelloService {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
- 字段编号:每个字段都有一个唯一的数字编号。这是必要的,因为在解析过程中,我们需要知道每个字段的顺序。在
.proto
文件中定义的每个字段都有默认值。例如,int32
类型的字段默认值为0
。 - 字段类型:每个字段都有一个类型。例如,
string
、int32
、message
等。对于message
类型的字段,你需要在括号内定义该消息的类型。对于repeated
类型的字段,你可以将多个值放入一个列表中。例如,Person
消息中的email
字段可以包含一个电子邮件地址列表。 - 服务调用:在客户端代码中,你可以使用生成的
stub
类来调用服务方法。例如,你可以这样调用SayHello
方法:
HelloService.stub stub = HelloServiceGrpc.newBlockingStub(channel);
HelloReply response = stub.sayHello(HelloRequest.newBuilder().build());
关键字
Protocol Buffers(Protobuf)的关键字包括:
- message:代表实体结构,由多个消息字段(field)组成。
- required:表示该字段是必需的。
- optional:表示该字段是可选的。
- repeated:表示该字段的值是一个列表,可以包含0个或多个元素。
- group:用于组织多个字段,但目前该关键字已被废弃,应使用message。
- syntax:用于指定协议版本号,没有指定则默认为proto2版本。
- package:相等于C++中的命名空间,为了防止名称冲突。
- import:引入其他的proto文件,可以使用其他proto文件中定义的消息类型。
- message:用于定义消息类型,相当于C++中的struct。
- enum:用于定义枚举类型,相当于C++中的enum。
- option:用于设置消息字段的选项,如default、packed等。
- extensions:用于定义扩展消息字段,可以在现有消息类型的基础上添加新的字段。
- rpc:用于定义远程过程调用(RPC)服务。
- service:用于定义服务接口。
- default :用于为字段设置默认值。
- packed :用于指示字段值应该进行压缩存储。
- max :用于指定枚举类型的最大值。
- value :用于指定枚举类型的值。
- returns :用于指定RPC服务的返回类型。
这些关键字在Protobuf中具有特定的含义和用途,根据需要选择使用。
此外,Protobuf中的字段定义包括数据类型、字段名称、字段规则等部分。每个字段的定义由一定的格式构成,包括数据类型、字段名称、字段标识是必须定义的部分,字段默认值部分在proto3版本中不再支持。
JSON与Protocol Buffers互相转换
- 与JSON的区别
Protocol Buffers与JSON的区别主要体现在以下三个方面:
- 数据格式:JSON是文本格式,而protobuf是二进制格式。JSON数据冗余较大,例如,每条记录都需要包含"CName"和"Gender",据统计,JSON格式的数据至少有20%左右是无效的。而protobuf用二进制编码数据,且数据的格式是事先通过一个后缀名为.proto的文件指定的,因此protobuf的数据信息相对较少。
- 解析速度:由于protobuf对message没有动态解析,没有了动态解析的处理序列化速度自然快,通常protobuf的序列化速度是JSON的10倍左右。
- 功能:JSON只是一种数据表示法,而protobuf则提供模式(类型),建立文档,强制正确的用法。
总的来说,JSON和protobuf在数据格式、解析速度和功能上存在明显差异。具体选择使用哪种数据存储格式,取决于具体的应用场景和需求。
- 互相转换案例
在Java中,我们可以使用一些库来实现JSON与Protocol Buffers之间的相互转换。这里以Google的Protocol Buffers和Jackson库为例进行介绍。
首先,确保你的项目中已经添加了Protocol Buffers和Jackson的依赖。如果你使用Maven,可以在pom.xml
文件中添加以下依赖:
<dependencies>
<!-- Protocol Buffers -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.17.3</version> <!-- 请检查是否有更新的版本 -->
</dependency>
<!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0</version> <!-- 请检查是否有更新的版本 -->
</dependency>
</dependencies>
- Protocol Buffers转JSON
首先,我们需要将Protocol Buffers的消息转换为Java对象。假设我们有一个Person
消息:
syntax = "proto3";
package tutorial;
message Person {
string name = 1;
int32 id = 2;
string email = 3;
}
编译这个.proto
文件,生成Java代码。然后,我们可以使用Jackson库将Java对象转换为JSON:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.protobuf.util.JsonFormat;
import tutorial.PersonOuterClass.Person;
public class ProtobufToJsonExample {
public static void main(String[] args) throws Exception {
Person person = Person.newBuilder()
.setId(1234)
.setName("Alice")
.setEmail("alice@example.com")
.build();
ObjectMapper objectMapper = new ObjectMapper();
String jsonString = JsonFormat.printer().print(person);
System.out.println(jsonString); // 输出JSON字符串
}
}
- JSON转Protocol Buffers
同样地,我们可以将JSON字符串转换为Java对象后,再将其转换为Protocol Buffers消息:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.protobuf.util.JsonFormat;
import tutorial.PersonOuterClass.Person;
public class JsonToProtobufExample {
public static void main(String[] args) throws Exception {
String jsonString = "{\"id\":1234,\"name\":\"Alice\",\"email\":\"alice@example.com\"}";
ObjectMapper objectMapper = new ObjectMapper();
Person person = objectMapper.readValue(jsonString, Person.class);
System.out.println(person); // 输出Person消息对象
}
}
在Java中使用Protocol Buffers可以方便地进行数据序列化和反序列化。以下是一个简单的使用案例:
假设我们有一个Person消息类型,包含姓名、年龄和电子邮件地址等字段。我们可以使用Protocol Buffers的Java API来序列化和反序列化这个消息类型。
首先,我们需要定义Person消息类型的.proto文件,如下所示:
syntax = "proto3";
package tutorial;
message Person {
string name = 1;
int32 age = 2;
string email = 3;
}
然后,我们需要使用Protocol Buffers的编译器protoc将.proto文件编译成Java代码。命令如下:
protoc --java_out=./ ./person.proto
这将生成一个PersonOuterClass.java和一个Person.java文件,其中Person.java包含Person消息类型的定义和相关方法。我们可以将这些代码导入到我们的Java应用程序中。
接下来,我们可以使用Java代码来序列化和反序列化Person消息类型。以下是一个简单的例子:
import tutorial.PersonOuterClass.Person;
import com.google.protobuf.util.JsonFormat;
public class PersonProtobufExample {
public static void main(String[] args) throws Exception {
// 创建一个Person对象
Person person = Person.newBuilder()
.setId(1234)
.setName("Alice")
.setEmail("alice@example.com")
.build();
// 将Person对象序列化为字节数组
byte[] bytes = person.toByteArray();
// 将字节数组反序列化为Person对象
Person deserializedPerson = Person.parseFrom(bytes);
// 输出反序列化后的Person对象
System.out.println(deserializedPerson);
}
}
这个例子中,我们创建了一个Person对象,将其序列化为字节数组,然后将字节数组反序列化为Person对象,并输出反序列化后的Person对象。
gRPC与Protocol Buffers的关系
Protocol Buffers(protobuf)是由 Google 开发的一种数据序列化协议(类似于 XML、JSON、YAML 等),它可以将结构化的数据序列化成字节流,以便在网络上进行传输或存储到文件或数据库中。
gRPC 是一个高性能、开源、通用的 RPC(远程过程调用)框架,它基于 protobuf 实现,使用 protobuf 序列化协议来序列化和反序列化消息。gRPC 支持多种语言,包括 Java、C++、Python、Go、Ruby、PHP 等,可以跨语言进行通信。
因此,gRPC 和 Protocol Buffers 的关系是,protobuf 是 gRPC 使用的一种数据序列化协议,gRPC 利用 protobuf 将数据序列化成字节流,以便在网络上进行传输或存储到文件或数据库中。同时,gRPC 也提供了接口和工具,使得用户可以方便地使用 protobuf 进行数据序列化和反序列化操作。
gRPC的简单介绍