Skip to content

在写gRpc的时候,离不开proto文件,proto文件的语法比较简单,和api文件差不多,go-zero的官网也有示例,所以,过多的内容我就不写了,这里记录一下,我的一些疑问,其实也是一些无关紧要的问题

protobuf
syntax = "proto3";

package blog;

// 生成 golang 代码后的包名,以及代码位置
option go_package = "../proto/blog";

// 定义一个博客消息类型,包含 id、title 和 content 字段
message Blog {
  int64 id = 1;
  string title = 2;
  string content = 3;
}

// 定义一个分页请求消息类型,包含 page 和 page_size 字段
message PageRequest {
  int32 page = 1;
  int32 page_size = 2;
}

// 定义一个分页响应消息类型,包含 total 和 blogs 字段
message PageResponse {
  int32 total = 1;
  repeated Blog blogs = 2;
}

// 定义一个 BlogService 服务,包含四个方法
service BlogService {
  // 定义 GetBlogList 方法,参数为 PageRequest 类型,返回值为 PageResponse 类型
  rpc GetBlogList (PageRequest) returns (PageResponse) {}

  // 定义 GetBlog 方法,参数为 int64 类型(博客 ID),返回值为 Blog 类型
  rpc GetBlog (int64) returns (Blog) {}

  // 定义 CreateBlog 方法,参数为 Blog 类型,返回值为 Blog 类型
  rpc CreateBlog (Blog) returns (Blog) {}

  // 定义 UpdateBlog 方法,参数为 Blog 类型,返回值为 Blog 类型
  rpc UpdateBlog (Blog) returns (Blog) {}
}

下面就写我的疑问了,答案都是问chatgpt所得出的

  1. 定义消息时,后面的1,2,3是什么东西

    在 Protobuf 消息定义中,123 等数字表示每个字段的标识符(field tag)。这些标识符用于标识消息中的每个字段,并在编码和解码时被使用。

    每个字段都必须有一个唯一的标识符,它们是在消息定义中定义的,而不是在消息实例中。标识符是一个非负整数,通常采用 1、2、3 等连续的数字分配给每个字段,但是你可以根据需要自由分配它们。需要注意的是,标识符不能为 0,因为 0 表示一个无效的标识符。

    在消息编码时,每个字段都会被编码为一个键值对,其中键是标识符,值是字段的值。接收方在解码消息时,会根据标识符来识别每个字段的值。因此,标识符是保证消息编码和解码正确性的重要元素之一。

    通常情况下,当你在定义消息时不显式地分配标识符时,Protobuf 编译器会自动为每个字段分配一个标识符,通常是按照它们在消息定义中出现的顺序分配。但是,为了保证消息定义的稳定性,建议显式地为每个字段分配标识符,并将它们包含在消息定义中。这样,即使你在消息中添加或删除字段,也不会影响已经定义的字段的标识符。

  2. 这个1,2,3的大小顺序对实际生成的二进制数据有没有影响

    在 Protobuf 中,字段标识符的大小顺序不会影响实际生成的二进制数据。无论你按照什么顺序为消息中的字段分配标识符,生成的二进制数据都会按照以下规则进行编码:

    1. 每个字段的标识符和值都被编码为一个键值对,其中键和值之间用特定的编码方式进行分隔。

    2. 按照字段在消息定义中出现的顺序,将所有字段的键值对按照标识符升序排序。

    3. 对于每个字段的键值对,首先编码标识符,然后编码值,最后将它们合并成一个完整的字节序列。

    因此,标识符的大小顺序只影响了键值对在二进制编码中的顺序,而不会影响字段值的编码方式和二进制数据的生成。在解码消息时,接收方会根据标识符来识别每个字段的值,而不依赖于键值对在二进制编码中的顺序。

    需要注意的是,虽然标识符的大小顺序不会影响二进制数据的生成,但是在改变消息定义时,如果你改变了字段的标识符,那么这可能会影响到已经存储的消息的解码。因为接收方在解码消息时,必须使用正确的标识符来识别每个字段的值。因此,建议在消息定义时为每个字段分配一个稳定的标识符,并在消息的生命周期内保持不变。

protobuf
message Person {
  string name = 1001;
  int32 age = 2111;
}
message Person {
  string name = 1;
  int32 age = 2;
}
这两者对于同样的数据,生成的二进制数据相同嘛?

对于同样的数据,这两个消息定义生成的二进制数据是不同的。

在 Protobuf 中,每个字段都必须有一个唯一的标识符,用于标识该字段。在这两个消息定义中,nameage 字段分配了不同的标识符,因此它们的编码方式也不同。

下面是这两个消息定义对应的二进制编码示例:

对于第一个消息定义:

protobuf
message Person {
  string name = 1001;
  int32 age = 2111;
}

对于包含 name 字段值为 "Alice"age 字段值为 30Person 消息,它的二进制编码可能是这样的:

xF9x07x0A x05xAlice xxE3x0F x1E

这里的 xF9x07xE3x0F 分别是 nameage 字段的标识符,它们分别对应着 nameage 字段的新标识符 10012111。在解码消息时,接收方会根据标识符来识别每个字段的值。

对于第二个消息定义:

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

对于包含 name 字段值为 "Alice"age 字段值为 30Person 消息,它的二进制编码可能是这样的:

x0A x05xAlice x10 x1E

这里的 x0Ax10 分别是 nameage 字段的标识符,它们分别对应着 nameage 字段的新标识符 12。这个编码方式与第一个消息定义生成的编码不同,因为它们使用了不同的标识符。

因此,如果你要在 Protobuf 中定义消息类型,需要确保每个字段都有唯一的标识符,并在消息定义中保持不变,以便正确地编码和解码消息。

protobuf
message Person {
  int32 age = 2;
  string name = 1;
}
message Person {
  string name = 1;
  int32 age = 2;
}
这两者对于同样的数据,生成的二进制数据相同嘛?

对于同样的数据,这两个消息定义生成的二进制数据是相同的。

在 Protobuf 中,字段的顺序不影响实际生成的二进制数据,因为在编码和解码消息时,每个字段都会被编码为一个键值对,其中键是标识符,值是字段的值。在编码时,所有字段的键值对会按照标识符升序排序,而不依赖于它们在消息定义中出现的顺序。因此,无论你在消息定义中如何排列字段的顺序,生成的二进制数据都会是相同的。

因此,对于这两个消息定义:

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

在编码包含 name 字段值为 "Alice"age 字段值为 30Person 消息时,生成的二进制数据都将是相同的。这是因为无论你定义的消息结构如何,编码方式都是相同的,并且在解码时接收方会根据标识符来识别每个字段的值,而不依赖于它们在消息定义中出现的顺序。

需要注意的是,当你在修改消息定义时,如果你改变了字段的顺序,那么这可能会影响到已经存储的消息的解码。因此,建议在消息定义时为每个字段分配一个稳定的标识符,并在消息的生命周期内保持不变,以避免对消息的解码产生影响。