Google 的 Protobuf, 快就完事啦

不要说,离开以后还会想念;不要说,分手以后还是朋友。离开一个地方,风景就不再属于你;错过一个人,那人便与你无关

Google 的 Protobuf

Protocol Buffers(简称 Protobuf)是 Google 开发的一种数据序列化和反序列化格式。它用于在网络通信中高效地传输结构化数据。

Protobuf 是一种二进制格式,相比于 JSON 或 XML,它更紧凑、更快速,且解析开销更低。Protobuf 广泛应用于远程过程调用(RPC)、数据存储和传输等场景。

以下是 Protobuf 的一些主要特点:

  1. 高效:Protobuf 采用二进制编码,因此文件体积更小,传输速度更快,解析和生成开销更低。
  2. 语言无关:Protobuf 支持多种编程语言,包括 C++, C#, Go, Java, JavaScript, PHP, Python, Ruby 等。这使得在不同语言编写的程序之间交换数据变得更加容易。
  3. 向后兼容:Protobuf 允许数据结构的无缝升级。您可以向数据结构添加新字段,而不会破坏现有的程序,这有助于更灵活地进行软件维护和迭代。
  4. 结构化数据:Protobuf 通过定义数据结构(称为消息)来描述数据。这使得数据可以在不同的系统之间以一致的格式传输和处理。

使用 Protobuf 时,首先需要定义数据结构(消息)。消息定义文件通常使用 .proto 扩展名,例如 message.proto。下面是一个简单的 Protobuf 消息定义示例:

syntax = "proto3";

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

此示例定义了一个名为 Person 的消息,包含三个字段:nameageemail。每个字段都有一个唯一的标签(例如 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 工具。

  1. protobuf-jetbrains-plugin:这是一个 IntelliJ IDEA 插件,它可以根据 Java 类自动生成 Protobuf 消息定义。您可以在 IntelliJ IDEA 插件市场搜索并安装它。安装后,在 Java 类编辑器中右键单击,选择 “Generate” > “Generate .proto file”,即可自动生成对应的 .proto 文件。
  2. 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 不直接支持这些概念,但可以通过一些方法来实现类似的功能。

  1. 嵌套类: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;
    }
}

在这个例子中,DogCat 都包含一个 Animal 类型的字段,实现了类似于类继承的功能。同时,Pet 消息使用 oneof 构造表示了类似于接口实现的概念,允许 PetDogCat 类型的实例。

请注意,Protobuf 本质上是一个结构化数据表示,因此它的表示方法不会与传统面向对象编程完全一致。然而,在实际应用中,您可以通过上述方式在 Protobuf 中表示嵌套类、集合以及类似于类继承和接口实现的概念。

其他高级用法吗?

除了基本的数据结构和类型,Protobuf 还有一些高级用法,这些用法可以帮助您更好地表示和处理数据。以下是一些 Protobuf 的高级用法:

  1. 导入其他 .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 字段可以是 textnumberflag 中的一个。在同一时间,只能设置一个字段的值。

这些高级用法可以帮助您更好地组织和表示数据,以及实现复杂的 API 和通信模式。尽管它们不是必需的,但在某些情况下,这些高级用法可以使 Protobuf 更具灵活性和表达力。

当然,除了之前提到的高级用法,还有一些其他的高级功能,可以帮助您更好地利用 Protobuf。以下是一些额外的高级用法:

  1. 包(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 提供了用于表示时间和持续时间的特殊类型,称为 TimestampDuration。这些类型可以帮助您以标准化的方式表示日期和时间。例如:

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 时遵循正确的命名规范。

  1. 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 的枚举类型,包含三个可能的值:UNKNOWNACTIVEINACTIVEstatus 字段使用了这个枚举类型。
3. 分组(已弃用):在 Proto2 中,您可以使用分组(Group)来将相关字段组织在一起。然而,分组已经在 Proto3 中弃用,因此不建议使用。改为使用嵌套消息来组织相关字段。

这些高级用法有助于更好地表示和处理数据,提高代码的可读性和可维护性。虽然您可能不会在所有项目中使用这些功能,但了解它们可以帮助您在需要时更有效地利用 Protobuf。

Protobuf的主要用途是什么吗?

Protocol Buffers(Protobuf)是 Google 开发的一种数据序列化格式,它主要用于在不同系统之间高效地传输结构化数据。Protobuf 的设计目标是提供一种紧凑、快速且可扩展的数据表示格式。在实际开发中,Protobuf 的主要用途包括:

  1. 数据交换:Protobuf 可以作为一种数据交换格式在不同系统、平台和编程语言之间传输数据。与 JSON、XML 等文本格式相比,Protobuf 通常具有更小的体积和更快的解析速度。
  2. API 定义:Protobuf 支持定义服务和 RPC,这使得它可以用于描述 API 接口。通过使用 Protobuf 作为 API 的接口定义语言,开发者可以在不同编程语言和平台之间轻松地实现通信。
  3. 配置文件:Protobuf 可以用于存储配置文件,这使得配置文件可以轻松地跨平台和编程语言共享。
  4. 数据存储:Protobuf 可以用于存储结构化数据,例如数据库中的记录。与 JSON、XML 等文本格式相比,Protobuf 具有更好的性能和更小的存储开销。

实际开发中有哪些成功案例呢?

在实际开发中,有许多成功案例使用了 Protobuf 作为数据交换格式。以下是一些例子:

  1. gRPC:gRPC 是一种高性能、开源的通用 RPC 框架,由 Google 开发。它使用 Protobuf 作为接口定义语言,支持多种编程语言和平台。gRPC 被广泛应用于微服务架构、API 开发以及跨平台通信。
  2. TensorFlow:TensorFlow 是 Google 开发的一个开源机器学习框架。在 TensorFlow 中,Protobuf 用于表示计算图、训练参数以及其他相关数据结构。通过使用 Protobuf,TensorFlow 可以轻松地在不同平台和编程语言之间共享模型和配置。
  3. etcd:etcd 是一个分布式键值存储系统,用于存储分布式系统的配置数据和服务发现信息。etcd 使用 Protobuf 存储键值对数据,以提高存储效率和性能。
  4. Apache Arrow:Apache Arrow 是一个跨语言开发平台,用于加速大数据分析和计算。Arrow 使用 Protobuf 定义元数据和模式,这使得在不同系统和编程语言之间共享数据变得更加容易。

这些成功案例表明,Protobuf 在许多领域都有广泛的应用,如通信、大数据、机器学习等。通过使用 Protobuf,开发者可以提高数据交换的效率,简化跨平台和跨语言的通信。

Spring Boot 中使用 Protobuf 作为 Redis 的序列化和反序列化工具

在 Spring Boot 中使用 Protobuf 作为 Redis 的序列化和反序列化工具,首先需要添加相关的依赖,然后配置 RedisTemplate。以下是详细的步骤:

  1. 添加依赖

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>
  1. 创建 .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;
}
  1. 生成 Java 类

使用 protoc 编译器将 .proto 文件编译成对应的 Java 类。例如:

protoc --java_out=./src/main/java person.proto

这将在 src/main/java/com/example/protobuf 目录下生成一个名为 PersonProto.java 的文件。

  1. 配置 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;
    }
}
  1. 使用 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 具有更高的性能,主要原因如下:

  1. 二进制格式:Protobuf 是一种二进制格式,而 JSON 和 XML 是文本格式。二进制格式通常比文本格式具有更小的体积,因为它不需要额外的字符(如引号和标签)来表示数据结构。由于数据包的大小更小,因此在网络传输过程中的开销也更低。
  2. 高效的编码和解码:Protobuf 使用了一种紧凑的、高效的编码方案来表示数据。例如,整数使用可变长度编码,使得较小的整数占用更少的空间。这种编码方案不仅降低了数据的存储空间,还使得编码和解码过程更加高效。相比之下,JSON 和 XML 使用文本表示法,需要额外的解析和转换过程。
  3. 预先生成的代码:Protobuf 使用 .proto 文件定义数据结构,然后通过编译器生成对应的编程语言代码(如 Java、C++、Python 等)。这种生成的代码针对特定的数据结构进行了优化,因此在序列化和反序列化过程中具有很高的性能。相反,JSON 和 XML 通常依赖于通用的解析器和序列化器,可能无法充分优化特定数据结构。
  4. 静态类型检查:Protobuf 的数据结构在编译时被明确定义,因此可以在编译时捕获类型错误。这不仅可以提高代码的可读性和可维护性,还可以减少在运行时处理类型错误的开销。JSON 和 XML 则是动态类型的,需要在运行时进行类型检查。
  5. 定制序列化和反序列化:Protobuf 允许开发者为特定的数据结构定制序列化和反序列化逻辑,从而进一步提高性能。而 JSON 和 XML 通常使用通用的序列化和解析库,可能无法针对特定场景进行优化。

综上所述,Protobuf 的性能优势主要来源于其二进制格式、高效的编码和解码、预先生成的代码、静态类型检查以及定制序列化和反序列化逻辑。因此,对于需要高性能和紧凑数据表示的场景,使用 Protobuf 作为序列化和反序列化工具通常比 JSON 和 XML 更加合适。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值