Protobuf 序列化/反序列化 是真快呀 🏫
- Google 的 Protobuf
- 多对象序列化
- 如果类里面嵌套另一个类, 或者包含List/Map/Set集合, 或者类继承某个类呢
- 其他高级用法吗?
- Protobuf的主要用途是什么吗?
- 实际开发中有哪些成功案例呢?
- Spring Boot 中使用 Protobuf 作为 Redis 的序列化和反序列化工具
- 使用 Protobuf 作为序列化反序列化为什么比json/xml快
不要说,离开以后还会想念;不要说,分手以后还是朋友。离开一个地方,风景就不再属于你;错过一个人,那人便与你无关
Google 的 Protobuf
Protocol Buffers(简称 Protobuf)是 Google 开发的一种数据序列化和反序列化格式。它用于在网络通信中高效地传输结构化数据。
Protobuf 是一种二进制格式,相比于 JSON 或 XML,它更紧凑、更快速,且解析开销更低。Protobuf 广泛应用于远程过程调用(RPC)、数据存储和传输等场景。
以下是 Protobuf 的一些主要特点:
- 高效:Protobuf 采用二进制编码,因此文件体积更小,传输速度更快,解析和生成开销更低。
- 语言无关:Protobuf 支持多种编程语言,包括 C++, C#, Go, Java, JavaScript, PHP, Python, Ruby 等。这使得在不同语言编写的程序之间交换数据变得更加容易。
- 向后兼容:Protobuf 允许数据结构的无缝升级。您可以向数据结构添加新字段,而不会破坏现有的程序,这有助于更灵活地进行软件维护和迭代。
- 结构化数据:Protobuf 通过定义数据结构(称为消息)来描述数据。这使得数据可以在不同的系统之间以一致的格式传输和处理。
使用 Protobuf 时,首先需要定义数据结构(消息)。消息定义文件通常使用 .proto
扩展名,例如 message.proto
。下面是一个简单的 Protobuf 消息定义示例:
syntax = "proto3";
message Person {
string name = 1;
int32 age = 2;
string email = 3;
}
此示例定义了一个名为 Person
的消息,包含三个字段:name
、age
和 email
。每个字段都有一个唯一的标签(例如 1、2、3),这些标签在二进制编码中用于表示字段。
定义好数据结构后,需要使用 Protobuf 编译器(protoc
)将 .proto
文件编译成对应编程语言的代码。这些生成的代码包含消息的序列化和反序列化逻辑,可以轻松地在程序中使用。
例如,使用以下命令将 message.proto
编译成 Python 代码:
protoc --python_out=. message.proto
这将生成一个名为 message_pb2.py
的文件,其中包含 Person
消息的 Python 类。然后,可以在 Python 程序中使用这个类来序列化和反序列化数据。
总之,Google 的 Protobuf 是一种高效、语言无关的数据序列化格式,适用于跨平台和高性能的数据交换场景。
多对象序列化
如果您有很多对象需要使用 Protobuf 进行序列化,您可以在一个 .proto
文件中定义多个消息(类似于类)。然而,对于已经存在的对象(例如 Java POJO 类),您需要将它们转换为 Protobuf 消息格式。
手动为每个对象创建 .proto
文件可能会很繁琐。幸运的是,有一些工具可以帮助您根据现有的对象生成 Protobuf 消息定义。这些工具通常针对特定编程语言,例如 Java 或 Python。
对于 Java,您可以使用 protobuf-jetbrains-plugin
插件(适用于 IntelliJ IDEA)或 protostuff
工具。
- protobuf-jetbrains-plugin:这是一个 IntelliJ IDEA 插件,它可以根据 Java 类自动生成 Protobuf 消息定义。您可以在 IntelliJ IDEA 插件市场搜索并安装它。安装后,在 Java 类编辑器中右键单击,选择 “Generate” > “Generate .proto file”,即可自动生成对应的
.proto
文件。 - protostuff:这是一个 Java 序列化库,它提供了一种将 Java POJO 类转换为 Protobuf 消息定义的方法。要使用 protostuff,您需要将其添加到项目的依赖项中。对于 Maven 项目,请将以下依赖项添加到
pom.xml
文件:
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>
<version>1.7.4</version>
</dependency>
使用 protostuff,您可以根据 Java 类自动生成 Protobuf 消息定义,如下所示:
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.ProtobufIOUtil;
import com.dyuproject.protostuff.runtime.RuntimeSchema;
public class Example {
public static void main(String[] args) {
// 使用 RuntimeSchema 自动生成 Protobuf schema
Schema<MyPojoClass> schema = RuntimeSchema.getSchema(MyPojoClass.class);
MyPojoClass pojo = new MyPojoClass();
// ... 初始化 pojo 对象 ...
// 使用 ProtobufIOUtil 序列化 POJO 对象
byte[] serializedData = ProtobufIOUtil.toByteArray(pojo, schema, LinkedBuffer.allocate(256));
// 使用 ProtobufIOUtil 反序列化 POJO 对象
MyPojoClass deserializedPojo = schema.newMessage();
ProtobufIOUtil.mergeFrom(serializedData, deserializedPojo, schema);
}
}
请注意,protostuff 是一个运行时解决方案,它不会生成实际的 .proto
文件。但它允许您直接序列化和反序列化 Java POJO 类,而无需手动创建 Protobuf 消息定义。
对于其他编程语言,您可能需要搜索类似的库或工具。这些工具和库可以根据现有对象自动生成 Protobuf 消息定义,从而简化了在多个对象之间使用 Protobuf 的过程。
如果类里面嵌套另一个类, 或者包含List/Map/Set集合, 或者类继承某个类呢
Google Protobuf 支持表示嵌套类和集合,但在 Protobuf 中,这些概念和表示方式与传统的面向对象编程有所不同。对于类继承和接口实现,Protobuf 不直接支持这些概念,但可以通过一些方法来实现类似的功能。
- 嵌套类:Protobuf 支持嵌套消息(类似于嵌套类)。在
.proto
文件中,可以在一个消息内部定义另一个消息。例如:
syntax = "proto3";
message OuterClass {
message InnerClass {
string name = 1;
}
InnerClass inner = 1;
}
在这个例子中,OuterClass
包含了一个名为 InnerClass
的嵌套消息。
2. 集合(List/Map/Set):Protobuf 支持表示重复字段(类似于 List)和键值对映射(类似于 Map)。
- List:使用
repeated
关键字表示重复字段,例如:
syntax = "proto3";
message Example {
repeated int32 numbers = 1;
}
在这个例子中,numbers
字段表示一个整数列表。请注意,Protobuf 本身不支持 Set 类型,但可以通过消除重复元素的方法来实现 Set 类型。
- Map:使用
map
关键字表示键值对映射,例如:
syntax = "proto3";
message Example {
map<string, int32> dictionary = 1;
}
在这个例子中,dictionary
字段表示一个将字符串映射到整数的字典(类似于 Map)。
3. 类继承和接口实现:Protobuf 不直接支持类继承和接口实现的概念。然而,可以使用组合和消息字段的可选性来实现类似的功能。例如,可以通过将公共字段放在一个单独的消息中,然后将这个消息作为另一个消息的字段来实现类似于类继承的功能。对于接口实现,可以使用 oneof
构造来表示多种可能的实现类型。
下面是一个使用组合和 oneof
构造来实现类似类继承和接口实现的例子:
syntax = "proto3";
// 类似于基类
message Animal {
string name = 1;
}
// 类似于实现接口的不同类
message Dog {
Animal animal = 1;
string breed = 2;
}
message Cat {
Animal animal = 1;
string color = 2;
}
// 使用 oneof 表示接口实现类型
message Pet {
oneof pet_type {
Dog dog = 1;
Cat cat = 2;
}
}
在这个例子中,Dog
和 Cat
都包含一个 Animal
类型的字段,实现了类似于类继承的功能。同时,Pet
消息使用 oneof
构造表示了类似于接口实现的概念,允许 Pet
为 Dog
或 Cat
类型的实例。
请注意,Protobuf 本质上是一个结构化数据表示,因此它的表示方法不会与传统面向对象编程完全一致。然而,在实际应用中,您可以通过上述方式在 Protobuf 中表示嵌套类、集合以及类似于类继承和接口实现的概念。
其他高级用法吗?
除了基本的数据结构和类型,Protobuf 还有一些高级用法,这些用法可以帮助您更好地表示和处理数据。以下是一些 Protobuf 的高级用法:
- 导入其他
.proto
文件:在一个.proto
文件中,您可以使用import
语句导入其他.proto
文件中的消息定义。这有助于在多个文件中重用和组织代码。例如:
syntax = "proto3";
import "other.proto";
message Example {
OtherMessage other = 1;
}
在这个例子中,other.proto
文件中定义的 OtherMessage
类型被导入并用作 Example
消息的字段。
2. 自定义选项:您可以为 Protobuf 的字段、消息或枚举定义自定义选项。这些选项可以用于存储元数据或在代码生成时提供额外的信息。例如:
syntax = "proto3";
import "google/protobuf/descriptor.proto";
extend google.protobuf.FieldOptions {
optional string custom_option = 50000;
}
message Example {
string name = 1 [(custom_option) = "This is a custom option"];
}
在这个例子中,我们为 name
字段定义了一个名为 custom_option
的自定义选项。
3. 服务定义和 RPC:Protobuf 支持定义服务(Service)和远程过程调用(RPC)。这可以用于描述 API 的接口,便于在不同的编程语言和平台之间实现通信。例如:
syntax = "proto3";
import "example.proto";
service ExampleService {
rpc GetExample (ExampleRequest) returns (ExampleResponse);
}
message ExampleRequest {
int32 id = 1;
}
message ExampleResponse {
Example example = 1;
}
在这个例子中,我们定义了一个名为 ExampleService
的服务,包含一个名为 GetExample
的 RPC 方法。此方法接受 ExampleRequest
消息并返回 ExampleResponse
消息。
4. oneof
字段:oneof
字段允许在一组字段中选择一个。这可以用于表示联合类型(Union Types)或者在多个字段之间实现互斥(Mutually Exclusive)。例如:
syntax = "proto3";
message Example {
oneof value {
string text = 1;
int32 number = 2;
bool flag = 3;
}
}
在这个例子中,Example
消息的 value
字段可以是 text
、number
或 flag
中的一个。在同一时间,只能设置一个字段的值。
这些高级用法可以帮助您更好地组织和表示数据,以及实现复杂的 API 和通信模式。尽管它们不是必需的,但在某些情况下,这些高级用法可以使 Protobuf 更具灵活性和表达力。
当然,除了之前提到的高级用法,还有一些其他的高级功能,可以帮助您更好地利用 Protobuf。以下是一些额外的高级用法:
- 包(Package):在
.proto
文件中,可以使用package
关键字定义包名。这样可以避免命名冲突,同时在生成代码时可以将生成的类放入相应的命名空间。例如:
syntax = "proto3";
package mypackage;
message Example {
string name = 1;
}
在这个例子中,Example
消息将被放入名为 mypackage
的命名空间。
2. 默认值:Protobuf 支持为字段设置默认值。当字段未设置值时,可以使用默认值。例如:
syntax = "proto3";
message Example {
int32 id = 1 [default = 100];
}
在这个例子中,如果 id
字段未设置值,它的默认值将为 100。请注意,在 Proto3(Protobuf 的当前版本)中,所有字段都有默认值,因此这个功能主要适用于 Proto2。
3. 标量值类型的包装:在某些情况下,您可能希望将标量值类型(例如整数和字符串)作为消息字段。为了解决这个问题,Google 提供了一组“包装类型”(Wrapper Types),可以将标量值包装为消息。例如:
syntax = "proto3";
import "google/protobuf/wrappers.proto";
message Example {
google.protobuf.StringValue optional_name = 1;
}
在这个例子中,optional_name
字段使用了 google.protobuf.StringValue
包装类型,可以将字符串值包装为消息。
4. 时间和持续时间类型:Protobuf 提供了用于表示时间和持续时间的特殊类型,称为 Timestamp
和 Duration
。这些类型可以帮助您以标准化的方式表示日期和时间。例如:
syntax = "proto3";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
message Example {
google.protobuf.Timestamp created_at = 1;
google.protobuf.Duration duration = 2;
}
在这个例子中,created_at
字段使用了 google.protobuf.Timestamp
类型来表示时间戳,duration
字段使用了 google.protobuf.Duration
类型来表示持续时间。
5. JSON 映射:在处理 JSON 数据时,Protobuf 提供了与 JSON 之间的映射功能。使用 json_name
选项,您可以自定义 Protobuf 字段在 JSON 中的表示。例如:
syntax = "proto3";
message Example {
string first_name = 1 [json_name = "firstName"];
}
在这个例子中,first_name
字段在 JSON 中将被表示为 firstName
。这样可以帮助您在处理 JSON 数据时保持一致的命名约定和风格。通过在 Protobuf 消息中指定 JSON 名称,可以确保在从 Protobuf 消息转换为 JSON 时遵循正确的命名规范。
- Any 类型:
google.protobuf.Any
类型用于表示任意类型的消息。这在某些情况下非常有用,例如当您希望在不同类型的消息之间实现动态派发。使用Any
类型,您可以在单个字段中存储任意类型的消息。例如:
syntax = "proto3";
import "google/protobuf/any.proto";
message Example {
google.protobuf.Any payload = 1;
}
在这个例子中,payload
字段可以包含任意类型的消息。您可以在运行时检查消息的类型,并根据需要将其转换为正确的类型。
2. 枚举类型:枚举(Enum)类型用于表示一组命名的整数值。这对于表示一组有限的、离散的选项非常有用。例如:
syntax = "proto3";
message Example {
enum Status {
UNKNOWN = 0;
ACTIVE = 1;
INACTIVE = 2;
}
Status status = 1;
}
在这个例子中,我们定义了一个名为 Status
的枚举类型,包含三个可能的值:UNKNOWN
、ACTIVE
和 INACTIVE
。status
字段使用了这个枚举类型。
3. 分组(已弃用):在 Proto2 中,您可以使用分组(Group)来将相关字段组织在一起。然而,分组已经在 Proto3 中弃用,因此不建议使用。改为使用嵌套消息来组织相关字段。
这些高级用法有助于更好地表示和处理数据,提高代码的可读性和可维护性。虽然您可能不会在所有项目中使用这些功能,但了解它们可以帮助您在需要时更有效地利用 Protobuf。
Protobuf的主要用途是什么吗?
Protocol Buffers(Protobuf)是 Google 开发的一种数据序列化格式,它主要用于在不同系统之间高效地传输结构化数据。Protobuf 的设计目标是提供一种紧凑、快速且可扩展的数据表示格式。在实际开发中,Protobuf 的主要用途包括:
- 数据交换:Protobuf 可以作为一种数据交换格式在不同系统、平台和编程语言之间传输数据。与 JSON、XML 等文本格式相比,Protobuf 通常具有更小的体积和更快的解析速度。
- API 定义:Protobuf 支持定义服务和 RPC,这使得它可以用于描述 API 接口。通过使用 Protobuf 作为 API 的接口定义语言,开发者可以在不同编程语言和平台之间轻松地实现通信。
- 配置文件:Protobuf 可以用于存储配置文件,这使得配置文件可以轻松地跨平台和编程语言共享。
- 数据存储:Protobuf 可以用于存储结构化数据,例如数据库中的记录。与 JSON、XML 等文本格式相比,Protobuf 具有更好的性能和更小的存储开销。
实际开发中有哪些成功案例呢?
在实际开发中,有许多成功案例使用了 Protobuf 作为数据交换格式。以下是一些例子:
- gRPC:gRPC 是一种高性能、开源的通用 RPC 框架,由 Google 开发。它使用 Protobuf 作为接口定义语言,支持多种编程语言和平台。gRPC 被广泛应用于微服务架构、API 开发以及跨平台通信。
- TensorFlow:TensorFlow 是 Google 开发的一个开源机器学习框架。在 TensorFlow 中,Protobuf 用于表示计算图、训练参数以及其他相关数据结构。通过使用 Protobuf,TensorFlow 可以轻松地在不同平台和编程语言之间共享模型和配置。
- etcd:etcd 是一个分布式键值存储系统,用于存储分布式系统的配置数据和服务发现信息。etcd 使用 Protobuf 存储键值对数据,以提高存储效率和性能。
- Apache Arrow:Apache Arrow 是一个跨语言开发平台,用于加速大数据分析和计算。Arrow 使用 Protobuf 定义元数据和模式,这使得在不同系统和编程语言之间共享数据变得更加容易。
这些成功案例表明,Protobuf 在许多领域都有广泛的应用,如通信、大数据、机器学习等。通过使用 Protobuf,开发者可以提高数据交换的效率,简化跨平台和跨语言的通信。
Spring Boot 中使用 Protobuf 作为 Redis 的序列化和反序列化工具
在 Spring Boot 中使用 Protobuf 作为 Redis 的序列化和反序列化工具,首先需要添加相关的依赖,然后配置 RedisTemplate。以下是详细的步骤:
- 添加依赖
在 pom.xml
文件中添加以下依赖:
<dependencies>
<!-- Spring Boot Redis Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Protobuf -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.19.1</version>
</dependency>
</dependencies>
- 创建
.proto
文件
在项目中创建一个 .proto
文件,用于定义要序列化的数据结构。例如,创建一个名为 person.proto
的文件:
syntax = "proto3";
option java_package = "com.example.protobuf";
option java_outer_classname = "PersonProto";
message Person {
int32 id = 1;
string name = 2;
string email = 3;
}
- 生成 Java 类
使用 protoc
编译器将 .proto
文件编译成对应的 Java 类。例如:
protoc --java_out=./src/main/java person.proto
这将在 src/main/java/com/example/protobuf
目录下生成一个名为 PersonProto.java
的文件。
- 配置 RedisTemplate
创建一个配置类,用于配置 RedisTemplate,并将 Protobuf 作为序列化和反序列化工具:
import com.example.protobuf.PersonProto.Person;
import com.google.protobuf.InvalidProtocolBufferException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Person> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Person> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
// 使用 Protobuf 序列化器
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setValueSerializer(new RedisSerializer<Person>() {
@Override
public byte[] serialize(Person person) {
return person.toByteArray();
}
@Override
public Person deserialize(byte[] bytes) {
try {
return Person.parseFrom(bytes);
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException("Failed to deserialize Person object", e);
}
}
});
return redisTemplate;
}
}
- 使用 RedisTemplate
现在,可以在项目中使用 RedisTemplate 与 Redis 进行交互。例如,将一个 Person 对象存储到 Redis 中:
@Autowired
private RedisTemplate<String, Person> redisTemplate;
public void savePerson(Person person) {
redisTemplate.opsForValue().set("person:" + person.getId(), person);
}
public Person getPerson(int id) {
return redisTemplate.opsForValue().get("person:" + id);
}
通过这种方式,Spring Boot 中的 RedisTemplate 将使用 Protobuf 进行序列化和反序列化操作。这可以提高性能并减小存储开销,特别是在处理大量结构化数据时。
以下是一些其他示例,演示了如何使用 RedisTemplate 执行不同的 Redis 操作:
// 保存一个 Person 列表到 Redis
public void savePersons(List<Person> persons) {
Map<String, Person> personMap = new HashMap<>();
for (Person person : persons) {
personMap.put("person:" + person.getId(), person);
}
redisTemplate.opsForValue().multiSet(personMap);
}
// 获取一个 Person 列表从 Redis
public List<Person> getPersons(List<Integer> ids) {
List<String> keys = ids.stream().map(id -> "person:" + id).collect(Collectors.toList());
return redisTemplate.opsForValue().multiGet(keys);
}
// 更新一个 Person 对象
public void updatePerson(Person person) {
redisTemplate.opsForValue().set("person:" + person.getId(), person);
}
// 删除一个 Person 对象
public void deletePerson(int id) {
redisTemplate.delete("person:" + id);
}
// 判断 Person 对象是否存在
public boolean existsPerson(int id) {
return redisTemplate.hasKey("person:" + id);
}
这些示例展示了如何使用 RedisTemplate 执行基本的 CRUD 操作,如保存、获取、更新、删除和检查对象。通过将 RedisTemplate 配置为使用 Protobuf 进行序列化和反序列化,可以确保这些操作具有高性能和较小的存储开销。
总之,在 Spring Boot 中使用 Protobuf 作为 Redis 的序列化和反序列化工具可以提高应用程序的性能和可扩展性。通过简单地配置 RedisTemplate,您可以轻松地将 Protobuf 集成到您的项目中,并充分利用它的优势。
使用 Protobuf 作为序列化反序列化为什么比json/xml快
使用 Protobuf 作为序列化和反序列化工具相比于 JSON 和 XML 具有更高的性能,主要原因如下:
- 二进制格式:Protobuf 是一种二进制格式,而 JSON 和 XML 是文本格式。二进制格式通常比文本格式具有更小的体积,因为它不需要额外的字符(如引号和标签)来表示数据结构。由于数据包的大小更小,因此在网络传输过程中的开销也更低。
- 高效的编码和解码:Protobuf 使用了一种紧凑的、高效的编码方案来表示数据。例如,整数使用可变长度编码,使得较小的整数占用更少的空间。这种编码方案不仅降低了数据的存储空间,还使得编码和解码过程更加高效。相比之下,JSON 和 XML 使用文本表示法,需要额外的解析和转换过程。
- 预先生成的代码:Protobuf 使用
.proto
文件定义数据结构,然后通过编译器生成对应的编程语言代码(如 Java、C++、Python 等)。这种生成的代码针对特定的数据结构进行了优化,因此在序列化和反序列化过程中具有很高的性能。相反,JSON 和 XML 通常依赖于通用的解析器和序列化器,可能无法充分优化特定数据结构。 - 静态类型检查:Protobuf 的数据结构在编译时被明确定义,因此可以在编译时捕获类型错误。这不仅可以提高代码的可读性和可维护性,还可以减少在运行时处理类型错误的开销。JSON 和 XML 则是动态类型的,需要在运行时进行类型检查。
- 定制序列化和反序列化:Protobuf 允许开发者为特定的数据结构定制序列化和反序列化逻辑,从而进一步提高性能。而 JSON 和 XML 通常使用通用的序列化和解析库,可能无法针对特定场景进行优化。
综上所述,Protobuf 的性能优势主要来源于其二进制格式、高效的编码和解码、预先生成的代码、静态类型检查以及定制序列化和反序列化逻辑。因此,对于需要高性能和紧凑数据表示的场景,使用 Protobuf 作为序列化和反序列化工具通常比 JSON 和 XML 更加合适。