前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Go 开发者必备:Protocol Buffers 入门指南

Go 开发者必备:Protocol Buffers 入门指南

原创
作者头像
陈明勇
发布于 2025-01-21 17:29:39
发布于 2025-01-21 17:29:39
1.6K0
举报
文章被收录于专栏:Go 技术Go 技术Go技术干货

前言

在现代软件开发中,系统之间的高效通信至关重要,尤其是在微服务架构和分布式系统中。为了高效地传输数据并保证跨语言的兼容性,Protocol Buffers(简称 Protobuf) 应运而生。ProtobufGoogle 开发的一种轻量、高效的序列化数据格式。它被广泛应用于微服务、RPC 框架以及大数据处理等领域。

与传统的 JSONXML 格式相比,Protobuf 的优势在于其更小的体积和更快的速度。它通过定义消息结构(Schema)来进行数据的序列化和反序列化,支持多种编程语言,并且能够为开发人员提供一个明确且易于管理的数据传输模型。

本文将深入探讨如何在 Go 语言中使用 Protocol Buffers (Protobuf),全面覆盖从环境配置到实际应用的各个方面。我将逐步讲解如何安装和配置 Protobuf 编译器,编写和编译 .proto 文件,理解 Protobuf 的核心概念,如何定义和生成消息类型与服务接口。接着学习如何将其与 Go 结合,实现高效的序列化与反序列化操作。最后,文章还将介绍 Protobuf 的风格指南与最佳实践,帮助开发者在实际项目中更加规范、高效地使用 Protobuf

准备好了吗?准备一杯你最喜欢的咖啡或茶,随着本文一探究竟吧。

Protobuf 环境配置

安装 protobuf 编译器

Windows

1、下载 Protobuf

  • 访问 Protobuf GitHub Releases 页面。
  • 选择最新版本,并下载适用于 Windowsprotoc-<version>-win64.zipprotoc-<version>-win32.zip 文件。

2、解压

  • 解压下载的 ZIP 文件到你希望存放 protoc 的目录。

3、添加环境变量

  • protoc 所在的目录添加到系统的环境变量中。这样你就可以从命令行中的任何位置运行它。
  • 在“系统属性”中找到“环境变量”,然后在“Path”变量中添加 <protoc path>\bin 的路径。

4、验证安装

  • 打开命令行,输入 protoc --version,以检查是否安装成功。
代码语言:bash
AI代码解释
复制
$ protoc --version
libprotoc 29.3

MacOs

MacOs 系统上,你可以使用 Homebrew 安装 protoc

代码语言:bash
AI代码解释
复制
brew install protobuf

验证是否安装成功

代码语言:bash
AI代码解释
复制
$ protoc --version
libprotoc 29.3

Linux (Ubuntu/Debian)

在基于 Debian 的系统(如 Ubuntu)上,你可以使用 apt 安装 protoc

代码语言:bash
AI代码解释
复制
sudo apt install protobuf-compiler

验证是否安装成功

代码语言:bash
AI代码解释
复制
$ protoc --version
libprotoc 3.6.1

使用 apt 安装 protoc 时,会默认安装一个较为稳定的版本,该版本可能不是最新版本。因此,如果想要安装最新版本,建议使用其他的方式下载最新版本的发布包,然后进行安装。例如:

代码语言:bash
AI代码解释
复制
# 下载发布包
$ wget https://github.com/protocolbuffers/protobuf/releases/download/v25.1/protoc-25.1-linux-x86_64.zip
# 解压到 /usr/local/bin 目录下
$ unzip protoc-25.1-linux-x86_64.zip -d /usr/local/bin/protoc-25.1-linux-x86_64
# 配置环境变量
$ vim ~/.bashrc
# 添加以下内容
export PATH=$PATH:/usr/local/bin/protoc-25.1-linux-x86_64/bin
# 激活配置文件
$ source ~/.bashrc
# 验证是否安装成功
$ protoc --version
libprotoc 25.1

安装 protoc-gen-go

protoc-gen-goprotoc 的一个插件,用于生成 Go 语言的代码。

通过下面的命令进行安装:

代码语言:bash
AI代码解释
复制
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

验证是否安装成功:

代码语言:bash
AI代码解释
复制
$ protoc-gen-go --version
protoc-gen-go v1.31.0

初体验

首先在项目里面新建一个 proto 文件,假设文件名为 user.proto,然后定义消息类型

代码语言:txt
AI代码解释
复制
syntax = "proto3";
package tutorial;

option go_package = "github.com/chenmingyong0423/blog/tutorial-code/go/protobuf/proto/user";

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

然后执行以下命令,生成对应的 go 文件:

代码语言:shell
AI代码解释
复制
protoc --go_out=. --go_opt=paths=source_relative *.proto

这时我们就可以看到当前目录下多出了一个 user.pb.go 文件,该文件为 proto 代码编译后的 go 文件。

Protoc 命令常用参数

若要根据 proto 代码生成对应语言的代码(比如 Go),我们需要使用 protoc 命令,这个命令在之前已经给出安装教程。protoc 命令的常用参数如下所示:

  • -I--proto_path:指定 import 的文件查找路径,可以指定多个路径,例如 -Isrc -Iinclude。这样编译器会在这几个路径下查找 import.proto 文件。
  • --<language>_out:指定生成所指定的语言代码的输出目录,对于 Gogo_out=/directory
  • --<language>_opt:传递给指定语言插件的附加选项。作为 protoc 的插件,它们有着特定的参数选项,如果我们想指定某个参数选项,需要通过 <language>_opt 参数进行传递。例如:go_opt=paths=source_relative,传递 paths 参数选项给 protoc-gen-go 插件。

在大多数情况下,通过指定 <language>_out<language>_opt 参数,我们就可以满足代码生成的需求。值得一提的是,这些参数不限于单次使用;如果我们需要同时为多种语言生成代码,可以通过并行使用多个 <language>_opt<language>_opt 来实现这一目标。

若想了解更多的参数,可以运行 protoc --help 命令进行查看。

protoc-gen-go 插件参数

protoc-gen-go 是一个用于生成 Go 代码的插件,该插件有两个重要参数:

  • paths:控制 go 文件生成的路径
  • paths=import 时,输出文件将放置在 Go 包的导入路径命名 的目录中(导入路径.proto 文件中的 go_package 选项提供)。例如,Go 导入路径为 github.com/chenmingyong0423/blog/tutorial-code/go/protobuf/proto/user,那么输出的 .go 文件将放置在 github.com/chenmingyong0423/blog/tutorial-code/go/protobuf/proto/user/user.pb.go 。如果未指定 paths 参数,paths 的值将默认为 import
  • paths=source_relative 时,输出的 .go 文件将与 .proto 文件位于同一相对目录中。例如, .proto 文件位于 proto/user/user.proto,那么 .go 文将在 proto/user/user.pb.go 中生成。
  • module:如果指定了 module 参数,例如 module=examples,则生成的 .go 文件将位于 Go 包的导入路径 加上指定的模块目录下。例如,假设 Go 包的导入路径protobuf,并指定 module=examples,那么 .go 文件将生成在 protobuf/examples 目录中,例如:protobuf/examples/user.proto.go

protoc-gen-go 插件的参数需要通过 protoc 命令的 go_opt 参数进行传递,例如 go_opt=paths=source_relative

Protobuf 语法

定义消息类型

代码语言:go
AI代码解释
复制
syntax = "proto3";

option go_package = "github.com/chenmingyong0423/blog/tutorial-code/protobuf/examples";

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

通过 message 关键字定义一个消息类型。

字段定义

消息的字段定义格式为:[关键字] 类型 字段名 = 编号;,例如 string name = 1;optional string name = 1;

要点

  • 必须为消息定义中的每个字段指定一个 1536,870,911 之间的数字,并遵守以下限制:
    • 给定的编号在该消息的所有字段中必须是唯一的。
    • 字段编号 19,00019,999 被保留给 Protocol Buffers 实现。如果你在消息中使用了这些保留的字段编号,协议缓冲区编译器会报错。
    • 不能使用任何之前已经保留的字段编号,也不能使用已经分配给扩展的字段编号。
  • proto3 中,字段默认被标记为 optional,这意味着你可以不为某个字段赋值,它会使用该字段类型的默认值,同时也可以区分该字段是否被 赋值,即使该字段的值为默认值。

字段类型

标量类型(Scalar Types)

这些类型表示常见的数据类型,如整数、浮点数、布尔值、字符串等。

类型

默认值

备注

double

0.0

float

0.0

int32

0

32 位有符号整数,使用 变长编码(Variable-length encoding)。对于负数的编码效率较低。 如果字段值经常是负数,建议使用 sint32,因为 sint32 更有效地编码负数。

int64

0

64 位有符号整数,使用 变长编码(Variable-length encoding)。对于负数的编码效率较低。 如果字段值经常是负数,建议使用 sint64,因为 sint64 更有效地编码负数。

uint32

0

32 位无符号整数,使用 变长编码(Variable-length encoding)

uint64

0

64 位无符号整数,使用 变长编码(Variable-length encoding)

sint32

0

32 位有符号整数,使用 变长编码(Variable-length encoding)。与 int32 类似,但优化了负数的编码方式。

sint64

0

64 位有符号整数,使用 变长编码(Variable-length encoding)。与 int64 类似,但优化了负数的编码方式。

fixed32

0

始终使用 4 个字节进行编码。比 uint32 更有效,如果值大于 2<sup>28</sup>)。

fixed64

0

始终使用 8 个字节进行编码。比 uint64 更有效,如果值大于 2<sup>56</sup>)。

sfixed32

0

始终使用 4 个字节进行编码的有符号整数。

sfixed64

0

始终使用 8 个字节进行编码的有符号整数。

bool

false

布尔类型,只有两个值 truefalse

string

空字符串

字符串必须始终包含 UTF-8 编码或 7 位 ASCII 文本,并且长度不能超过 2<sup>32</sup>。

bytes

空字节

可以包含不超过 2<sup>32</sup> 的任意任意字节序列。

枚举类型(Enums)

枚举类型允许定义一组命名常量,通常用于表示状态、选项、类别等。

代码语言:txt
AI代码解释
复制
enum Status {
  PENDING = 0;
  IN_PROGRESS = 1;
  COMPLETED = 2;
}
  • 枚举值必须是 整数类型
  • 默认情况下,枚举值的第一个常量为 0,表示默认值。

消息类型(Message Types)

messageProtobuf 中的复合类型,用来表示一组相关的数据字段。每个字段可以是不同的类型,包括标量类型、枚举类型、其他消息类型等。

代码语言:txt
AI代码解释
复制
message User {
  string name = 1;
  int32 age = 2;
  string email = 3;
}
  • 嵌套消息:你还可以在一个消息中定义其他消息类型。
代码语言:txt
AI代码解释
复制
message AddressBook {
  message User {
    string name = 1;
    string email = 2;
  }
  repeated User user= 1;  // 这个字段是一个列表,包含多个 User
}

特殊类型

除了基本的标量、枚举以及消息类型,ProtoBuf 还提供了几种特殊的类型,用于处理更复杂的需求。

  • repeated:表示字段可以有多个值,相当于一个数组或列表。
代码语言:txt
AI代码解释
复制
message User {
  repeated string phones = 1; // 可以包含多个字符串
}
  • map:表示键值对集合,相当于字典或哈希表。键可以是标量类型(浮点类型和 bytes 除外),值可以是除另一个 map 之外的任何类型。。
代码语言:txt
AI代码解释
复制
message User {
  map <string, int32> scores = 1;
}

使用 map 类型的一些注意事项如下:

  • map 字段不能使用 repeated 关键字。
  • .proto 生成文本格式时,映射按键排序。数字键按数字排序。
  • map 的键值对在 wire 格式中的顺序以及在迭代时的顺序是未定义的,因此你不能依赖 map 中元素的顺序。
  • 在生成 .proto 的文本格式时,map 会按键进行排序。对于数值型的键,排序会按数字顺序进行。
  • 在解析 map 或进行合并时,如果出现重复的键,最后一个键值会被使用。在从文本格式解析时,如果遇到重复的键,解析可能会失败。
  • 如果你为 map 字段提供了一个键但没有提供值,则序列化时的行为取决于语言:
    • C++JavaKotlinPython 中,序列化时会使用该类型的默认值。
    • 在其他语言中,如果没有提供值,则该字段不会被序列化。
  • 在同一作用域中,不能存在一个名为 map foo 的字段和一个名为 FooEntry 的符号,因为 FooEntry 已经被用于 map 的实现。
  • Any:表示任意类型,它可以让字段存储不同类型的数据,而不需要在消息定义时提前知道这些类型。要使用 Any 类型,您需要导入 google/protobuf/any.proto
代码语言:txt
AI代码解释
复制
import "google/protobuf/any.proto";

message User {
  google.protobuf.Any data = 1;
}
  • oneof:一种特殊的字段类型,允许在一个消息中 定义多个字段,但在任何时候只能 设置其中一个字段。你可以添加任何类型的字段, map 字段和 repeated 字段除外。如果需要向 oneof 添加重复字段,可以使用包含重复字段的消息类型。
代码语言:txt
AI代码解释
复制
message MyMessage {
  oneof message_data {
    string text = 1;
    int32 number = 2;
    User user = 3;
  }
}

使用 oneof 类型的一些注意事项如下:

  • oneof 字段赋值时,它会自动清除同一 oneof 中的其他字段的值。
  • 如果解析时遇到同一个 oneof 中的多个字段,则只有最后一个字段会在解析的消息中保留其值。
    • 首先检查同一个 oneof 中的其他字段是否已经设置。如果有其他字段已设置,则清除它。
    • 然后按正常方式解析该字段,就好像它不属于 oneof 一样:
      • 基本类型 会覆盖已经设置的值。
      • 消息类型 会与已设置的值合并。
  • oneof 字段不能使用 repeated 关键字。
  • 反射 APIoneof 字段有效 你可以通过反射 API 来访问和修改 oneof 字段的值。
  • 如果你为 oneof 字段设置默认值(例如将 int32 类型的字段设置为 0),即使该字段的值是默认值,oneof 的 “case” 也会被设置,并且该值会被序列化到 wire 格式中。

定义服务

如果需要在 RPC(远程过程调用)系统中使用你的消息类型,可以在 .proto 文件中定义一个 RPC 服务接口,协议缓冲编译器会为你生成服务接口代码和存根代码,适用于你选择的编程语言。例如,如果你想定义一个 RPC 服务,包含一个方法,该方法接受 SearchRequest 并返回 SearchResponse,你可以在 .proto 文件中这样定义:

代码语言:txt
AI代码解释
复制
service SearchService {
  rpc Search(SearchRequest) returns (SearchResponse);
}

Go Protobuf 基础

掌握了 protobuf 基本的语法之后,接下来我们要了解 proto 代码与 go 代码之间的关系。下面将围绕着以下示例代码逐步进行讲解。

代码语言:txt
AI代码解释
复制
syntax = "proto3";
package tutorial;

import "google/protobuf/timestamp.proto";

option go_package = "github.com/chenmingyong0423/blog/tutorial-code/go/protobuf/protos/user";

message User {
    int32 id = 1;
    string name = 2;
    int32 age = 3;

    message PhoneNumber {
        string number = 1;
        PhoneType type = 2;
    }

    repeated PhoneNumber phones = 4;

    google.protobuf.Timestamp birth = 5;
}

enum PhoneType {
    // 个人手机
    PHONE_TYPE_MOBILE = 0;
    // 工作电话
    PHONE_TYPE_WORK = 1;
}

包声明

代码语言:txt
AI代码解释
复制
syntax = "proto3";
package tutorial;

option go_package = "github.com/chenmingyong0423/blog/tutorial-code/go/protobuf/protos/user";

.proto 文件以 package 声明开头,这有助于避免不同项目之间的命名冲突。然而,这里的 package 并不对应 Go 语言中的 package。协议缓冲编译器(protoc)会根据 .proto 文件中 go_package 字段的导入路径来确定 Go 代码中的包名,通常是该路径的最后一个部分。例如,基于示例代码生成的 Go 代码包名将是 user

包导入

如果在 .proto 文件中引入了标准库或第三方库,编译生成的 Go 代码中也会反映这一点。例如,若引入 google/protobuf/timestamp.proto,在 Go 代码中对应的导入路径为:

代码语言:go
AI代码解释
复制
timestamppb "google.golang.org/protobuf/types/known/timestamppb"

结构体

代码语言:go
AI代码解释
复制
message User {
    int32 id = 1;
    string name = 2;
    int32 age = 3;

    message PhoneNumber {
        string number = 1;
        PhoneType type = 2;
    }

    repeated PhoneNumber phones = 4;

    google.protobuf.Timestamp birth = 5;
}

enum PhoneType {
    // 个人手机
    PHONE_TYPE_MOBILE = 0;
    // 工作电话
    PHONE_TYPE_WORK = 1;
}

协议缓冲编译器(protoc)会将 protobuf 中的类型转换为 Go 语言中对应的类型。例如,message 类型会转换为 Go 中的 struct 结构体,而由于 Go 没有内建的枚举类型,enum 类型会被转换为 Go 的自定义类型。所生成的部分代码如下所示:

代码语言:go
AI代码解释
复制
type User struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Id     int32                  `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
	Name   string                 `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
	Age    int32                  `protobuf:"varint,3,opt,name=age,proto3" json:"age,omitempty"`
	Phones []*User_PhoneNumber    `protobuf:"bytes,4,rep,name=phones,proto3" json:"phones,omitempty"`
	Birth  *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=birth,proto3" json:"birth,omitempty"`
}

type PhoneType int32

const (
	// 个人手机
	PhoneType_PHONE_TYPE_MOBILE PhoneType = 0
	// 工作电话
	PhoneType_PHONE_TYPE_WORK PhoneType = 1
)

type User_PhoneNumber struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Number string    `protobuf:"bytes,1,opt,name=number,proto3" json:"number,omitempty"`
	Type   PhoneType `protobuf:"varint,2,opt,name=type,proto3,enum=PhoneType" json:"type,omitempty"`
}

Protobuf 类型与 Go 类型之间的映射关系

Protobuf 类型与 Go 类型之间有着明确的映射关系,理解这些映射关系对于正确使用 ProtobufGo 中非常重要。以下是一些常见的映射规则:

Protobuf 类型

Go 类型

double

float64

float

float32

int32

int32

int64

int64

uint32

uint32

uint64

uint64

sint32

int32

sint64

int64

fixed32

uint32

fixed64

uint64

sfixed32

int32

sfixed64

int64

bool

bool

string

string

bytes

[]byte

message

struct

enum

自定义类型(通常是 int32)

repeated

slice

map

map

读写消息示例

首先,我们需要创建一个名为 protobuf 的目录,并进入该目录初始化一个 Go 项目。接下来,在 proto/user 目录中创建一个名为 user.proto 的文件,文件内容使用之前提供的示例代码。项目目录结构如下所示:

代码语言:bash
AI代码解释
复制
.
├── go.mod
├── go.sum
└── proto
    └── user
        └── user.proto

然后在 proto 目录下,通过以下命令使用 protoc 编译 .proto 文件,生成对应的 Go 代码:

代码语言:bash
AI代码解释
复制
protoc --go_out=. --go_opt=paths=source_relative *.proto

接下来将基于生成的 Go 代码演示如何进行 Protobuf 消息的写入(序列化) 和 读取(反序列化) 操作。

在此之前,我们需要安装 proto 模块:

代码语言:bash
AI代码解释
复制
go get google.golang.org/protobuf/proto

序列化消息(写入)

代码语言:go
AI代码解释
复制
	// 写入消息
	user := pb.User{
		Id:   1,
		Name: "陈明勇",
		Age:  18,
		Phones: []*pb.User_PhoneNumber{
			{
				Number: "18888888888",
				Type:   pb.PhoneType_PHONE_TYPE_MOBILE,
			},
			{
				Number: "12345678901",
				Type:   pb.PhoneType_PHONE_TYPE_WORK,
			},
		},
		Birth: timestamppb.New(time.Date(1999, 1, 1, 0, 0, 0, 0, time.UTC)),
	}
	out, err := proto.Marshal(&user)
	if err != nil {
		panic(err)
	}
	err = os.WriteFile("user.bin", out, 0644)
	if err != nil {
		panic(err)
	}

反序列化消息(读取)

代码语言:go
AI代码解释
复制
	// 读取消息
	in, err := os.ReadFile("user.bin")
	if err != nil {
		panic(err)
	}
	user2 := &pb.User{}
	err = proto.Unmarshal(in, user2)
	if err != nil {
		panic(err)
	}
	// id:1 name:"陈明勇" age:18 phones:{number:"18888888888"} phones:{number:"12345678901" type:PHONE_TYPE_WORK} birth:{seconds:915148800}
	fmt.Println(user2)

完整示例:序列化与反序列化

新建 main.go 文件并写入以下内容:

代码语言:go
AI代码解释
复制
package main

import (
	"fmt"
	pb "github.com/chenmingyong0423/blog/tutorial-code/go/protobuf/proto/user"
	"os"
	"time"

	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/known/timestamppb"
)

func main() {
	// 写入消息
	user := pb.User{
		Id:   1,
		Name: "陈明勇",
		Age:  18,
		Phones: []*pb.User_PhoneNumber{
			{
				Number: "18888888888",
				Type:   pb.PhoneType_PHONE_TYPE_MOBILE,
			},
			{
				Number: "12345678901",
				Type:   pb.PhoneType_PHONE_TYPE_WORK,
			},
		},
		Birth: timestamppb.New(time.Date(1999, 1, 1, 0, 0, 0, 0, time.UTC)),
	}
	out, err := proto.Marshal(&user)
	if err != nil {
		panic(err)
	}
	err = os.WriteFile("user.bin", out, 0644)
	if err != nil {
		panic(err)
	}

	// 读取消息
	in, err := os.ReadFile("user.bin")
	if err != nil {
		panic(err)
	}
	user2 := &pb.User{}
	err = proto.Unmarshal(in, user2)
	if err != nil {
		panic(err)
	}
	// id:1 name:"陈明勇" age:18 phones:{number:"18888888888"} phones:{number:"12345678901" type:PHONE_TYPE_WORK} birth:{seconds:915148800}
	fmt.Println(user2)
}

通过 proto.Marshalproto.Unmarshal 函数,我们可以对 Protobuf 消息进行序列化(写入)和反序列化(读取)操作。

使用 go run main.go 命令,程序即可成功运行。

Protobuf 风格指南

为了确保 .proto 文件中协议缓冲消息定义及其对应类的结构一致且易于阅读。我们需要遵循这些规范。

需要注意的是,协议缓冲的风格在不断演进,因此我们可能会遇到采用不同风格或规范编写的 .proto 文件。在修改这些文件时,需要尽量遵循已有的风格,保持一致性是非常重要的。当然,在创建新的 .proto 文件时,建议采用当前最新的的最佳实践和风格。

标准文件格式

  • 保持每行字符长度不超过 80 个字符。
  • 使用 2 个空格作为缩进。
  • 字符串应优先使用双引号。

文件结构

文件名应采用小写蛇形命名法(lower_snake_case.proto)。

所有文件应按以下顺序组织:

  • 许可头(如果适用)
  • 文件概述
  • 语法版本
  • 包声明
  • 导入包(按字母顺序排序)
  • 文件选项
  • 其他内容

包声明

  • 包名应采用小写字母。
  • 包名应具有唯一性,通常基于项目名称,并且可以根据包含协议缓冲类型定义的文件路径进行命名。例如文件路径为 proto/user/user.proto,则包名可以是 proto.user

消息和字段命名

对于消息名称,使用 PascalCase(首字母大写)命名风格,例如 SongServerRequest。对于缩写,推荐将其为一个整体,保持首字母大写,而不是拆分字母:例如 GetDnsRequest,而不是 GetDNSRequestDns 作为一个整体,首字母大写。

对于字段名称(包括 oneof 字段和扩展名),使用 lower_snake_case(小写字母,单词间用下划线分隔):例如 song_name

Repeated 字段

Repeated 字段使用复数名称。例如 repeated string keys

Enum 字段

  • 枚举类型的命名
    • 使用 PascalCase(首字母大写)来命名枚举类型。例如:FooBar
  • 枚举值的命名
    • 使用 CAPITALS_WITH_UNDERSCORES(大写字母,并用下划线分隔)来命名枚举值。例如:FOO_BAR_UNSPECIFIEDFOO_BAR_FIRST_VALUE
  • 每个枚举值后应以分号结尾,而不是逗号。
  • 避免命名冲突:建议为每个枚举值加上枚举名称前缀或将枚举嵌套在消息内部。
  • 使用顶级枚举:如果可以,避免嵌套枚举。
  • 零值枚举:枚举的零值命名应为 UNSPECIFIED

服务(Service)

如果你的 .proto 文件中定义了 RPC 服务,应该对 服务名称RPC 方法名称 都使用 PascalCase(首字母大写)命名规则:

代码语言:txt
AI代码解释
复制
service FooService {
  rpc GetSomething(GetSomethingRequest) returns (GetSomethingResponse);
  rpc ListSomething(ListSomethingRequest) returns (ListSomethingResponse);
}

Protobuf 最佳实践

  • 不要重用标签号 不要重用标签号。重用标签号会导致反序列化错误。即使你认为没有人在使用该字段,也不要重用标签号,因为历史中可能已经有已序列化的 proto 数据,或者其他服务的旧代码可能会受到影响。
  • 删除字段后保留标签号 当你删除一个字段时,应该保留其标签号,以避免未来有人不小心重用该标签号。仅保留 23 等数字即可。你还可以保留已删除字段的名称,避免它们被重用:例如,reserved "foo", "bar";
  • 删除枚举值时保留标签号 同样,删除不再使用的枚举值时,应该保留它们的标签号,以免他人误用。可以像字段一样保留 23 等标签号,并保留已删除的枚举值名称:例如,reserved "FOO", "BAR";
  • 避免改变字段类型 除非是深思熟虑,否则不要改变字段的类型。这会导致反序列化失败。虽然有些类型的转换(如 int32uint32)是安全的,但改变消息类型会破坏兼容性,除非新类型是旧类型的超集。
  • 不要添加必填字段 永远不要添加必填字段,而应该通过文档注释来指定 API 合同的要求。proto3 移除了必填字段的支持,所有字段应当是可选的或重复的。这样可以避免未来需求变化时强制使用不再逻辑上需要的字段。
  • 不要创建包含大量字段的消息 尽量避免在同一消息中定义大量字段(例如:几百个字段)。过大的 proto 文件会增加内存使用,甚至可能导致生成的代码无法编译。建议将大型消息拆分为多个小的消息。
  • 为枚举添加一个未指定值 枚举应该包含一个默认的 FOO_UNSPECIFIED 值,作为枚举声明的第一个值。这样在添加新值时,旧客户端会将字段视为未设置,并返回默认值(即枚举的第一个值)。此外,枚举值应使用 tag 0 作为 UNSPECIFIED 的默认值。
  • 使用通用类型和常用类型 推荐使用一些已定义的通用类型(如 durationtimestampdatemoney 等),而不是自己定义类似的类型。这样可以减少重复定义,同时也能确保跨语言的一致性。
  • 在单独的文件中定义消息类型undefined 每个 proto 文件最好只定义一个消息、枚举、扩展、服务或循环依赖。将相关类型放在一个文件中会更容易进行重构和维护,也能确保文件不被过度膨胀。
  • 不要更改字段的默认值 永远不要更改字段的默认值,这样会导致客户端和服务端的版本不兼容。proto3 移除了为字段设置默认值的能力,因此,最好避免更改字段的默认值。
  • 避免将 repeated 类型转换为标量类型 不要将 repeated 字段改为标量类型,这样会丢失数据。对于 proto3 的数值类型字段,转换将会丢失字段数据。
  • 避免使用文本格式消息进行交换 文本格式(如 JSON 和文本格式)的序列化方法并不适合用于数据交换。它们将字段和枚举值表示为字符串,因此在字段或枚举值重命名或新增字段时,旧代码会导致反序列化失败。应尽可能使用二进制格式进行数据交换,文本格式仅限于调试和人工编辑。
  • 永远不要依赖于跨构建的序列化稳定性 Protobuf 的序列化稳定性无法保证跨不同的二进制文件或同一二进制文件的不同构建版本。不要依赖序列化稳定性来构建缓存键等。
  • 避免使用语言关键字作为字段名称 避免使用在目标语言中作为关键字的字段名称,因为这可能导致 protobuf 自动更改字段名称或提供特殊访问方式。还应避免在文件路径中使用关键字。

小结

本文介绍了如何在 Go 中使用 Protobuf,涵盖了环境配置、语法、集成步骤、风格指南和最佳实践等内容。通过本文,你可以快速上手 GoProtocol Buffers 的集成,掌握消息类型的定义、代码的生成以及消息的序列化与反序列化流程。

参考资料


你好,我是陈明勇,一名热爱技术、乐于分享的开发者,同时也是开源爱好者。

成功的路上并不拥挤,有没有兴趣结个伴?

关注我,加我好友,一起学习一起进步!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
浏览器指纹解读
指纹通常是指服务端的为了做识别而收集的客户端设备信息。即使没有cookie,指纹也可用于识别个人用户或设备。
李玺
2022/06/12
2.3K0
浏览器指纹解读
前端获取设备唯一编码方法
在前端开发中,出于安全和隐私的考虑,浏览器不允许直接获取硬件的唯一标识(如 MAC 地址、CPU 序列号等)。但可以通过以下方法生成设备指纹(Device Fingerprint),近似实现设备唯一标识:
龙小雨
2025/05/14
840
浏览器指纹:原来我们一直被互联网巨头监视,隐私在网上裸奔、无处可藏
一般情况下,网站或者广告商都想要一种技术可以在网络上精确的定位到每一个个体,就算你没有账号,没有登录,也可以通过收集这些个体的数据,然后加以分析之后更加精确的去推送广告和其他的一些活动。
夜尽天明
2020/03/20
4.9K0
大厂为啥都要用Node去写中间层(BFF)呢?
BFF是一种Web架构,全名为Backends For Frontends,即为服务于前端的后端。这个词来源于Sam Newman的一篇文章:Pattern: Backends For Frontends[1]。BFF一般指的是在前端与后端之间加增加一个中间层。为什么要在前端和后端之间增加一个BFF层呢?
zz_jesse
2023/10/28
2.3K0
大厂为啥都要用Node去写中间层(BFF)呢?
Chrome浏览器调试技巧大全!
注:本文测试、截图均为Edge浏览器(内核是Chromium),浏览器内核可了解《有哪些浏览器/内核?[1]》
zz_jesse
2024/07/04
4460
Chrome浏览器调试技巧大全!
爬虫浏览器的Cloudflare五秒盾处理
近期业务有不少涉及到国外的网站,本以为经受了和国内大量卷王公司对抗的考验之后,处理国外业务应该是降维打击才对。结果本地测试的时候的确很OK,但是一上线就发现全面飘红,多个不同业务同时出了 Cloudflare 著名的五秒盾:
mythsman
2022/12/13
5.8K0
爬虫浏览器的Cloudflare五秒盾处理
springboot第35集:微服务与flutter安卓App开发
在Linux或Unix系统中,您可以使用cat命令或tail命令来查看日志文件的内容。以下是常用的命令示例: 使用cat命令查看完整的日志文件内容: cat /path/to/your/logfile.log 使用tail命令查看日志文件的末尾部分(默认显示最后10行): tail /path/to/your/logfile.log 您也可以使用-n参数指定显示的行数,例如显示最后20行: tail -n 20 /path/to/your/logfile.log 如果日志文件比较大,可以使用less命令进行分页查看: less /path/to/your/logfile.log 使用space键向下翻页,使用b键向上翻页,使用q键退出查看。 如果您希望在实时监视日志文件的更新,可以使用tail命令的-f参数: tail -f /path/to/your/logfile.log 这将实时显示日志文件的末尾部分,并持续监视文件的更新。
达达前端
2023/10/08
2670
没登录网页也能个性化推荐?5分钟带你了解浏览器指纹
不知道大家有没有遇到这种场景,我无痕方式访问某个网站,该网站依然能够精准对我进行个性化推荐?这是为什么呢?接下来,本文将介绍浏览器指纹有关知识点
linwu
2023/09/14
4670
没登录网页也能个性化推荐?5分钟带你了解浏览器指纹
前端开发的利器,使用Whistle提升开发幸福感
好多人认为whistle是抓包工具,殊不知抓包只是whistle能力的冰山一角。除了抓包外,它还能修改请求与响应、真机调试h5移动端、解决跨域、域名映射等等等。总而言之,使用whistle能够提升我们的开发效率,改善开发体验。
winty
2024/04/15
4190
前端开发的利器,使用Whistle提升开发幸福感
当浏览器全面禁用三方 Cookie
苹果公司前不久对 Safari 浏览器进行一次重大更新,这次更新完全禁用了第三方 Cookie,这意味着,默认情况下,各大广告商或网站将无法对你的个人隐私进行追踪。而微软和 Mozilla 等也纷纷采取了措施禁用第三方 Cookie,但是由于这些浏览器市场份额较小,并没有给市场带来巨大的冲击。
ConardLi
2020/04/17
2.8K0
当浏览器全面禁用三方 Cookie
【拓展】937- 科普:探讨浏览器指纹
我们常说的指纹,都是指人们手指上的指纹,因具有唯一性,所以可以被用来标识一个人的唯一身份。而浏览器指纹是指仅通过浏览器的各种信息,如CPU核心数、显卡信息、系统字体、屏幕分辨率、浏览器插件等组合成的一个字符串,就能近乎绝对定位一个用户,就算使用浏览器的隐私窗口模式,也无法避免。
pingan8787
2021/04/26
9520
可怕的“浏览器指纹”,让你在互联网上,无处可藏
科技公司通过大数据,会对你进行一个大体的画像,然后按照你的喜好推送信息。比如一些精准的广告,刺激你荷尔蒙的小视频等。就拿你在玩的抖音来说,你其实可以匿名使用。你爱抖胸妹子的喜好,不会因为重装抖音而消失,它已熟知了你的癖好。
xjjdog
2019/12/26
9820
Canvas指纹隐藏实战
前两天和隔壁做风控的同学聊天,据说他们经常使用浏览器指纹来识别和标记爬虫(当然具体的细节是不能透露的),联想到我们最近也经常遇到被风控的情况,于是就花了点时间研究下浏览器指纹相关的知识。
mythsman
2022/11/14
3.7K0
Canvas指纹隐藏实战
一名中/高级前端工程师的自检清单-React 篇
你真的了解 React 吗?我们在面试中往往涉及 React 时,第一个问题就是“解释 React 是什么”。解释一种技术是什么,在面试中也是非常常见的引起 话题的题目。本篇文章我就带你掌握这一类概念题的解答技巧。
桃翁
2021/12/13
1.5K0
一名中/高级前端工程师的自检清单-React 篇
一行 Object.keys() 引发的血案
有一天上线后大佬反馈了一个问题,他刚发的动态在生成分享卡片的时候,卡片底部的小程序码丢失了,然而其他小伙伴都表示在自己手机上运行正常。事实上大佬也说除了这条动态以外,其它都是正常的。
coder_koala
2021/12/22
8080
一行 Object.keys() 引发的血案
被爬网站用fingerprintjs来对selenium进行反爬,怎么破?
Fingerprintjs实际上就是专门用来识别和追踪浏览器的,要应对起来,确实并非易事。那么,我们要如何应对FingerprintJS的唯一标记技术呢?
阿秋数据采集
2024/07/31
7200
新时代前端农民工应该怎么准备面试(二)
文章转载至子弈文章:https://juejin.cn/post/6996815121855021087
桃翁
2021/09/09
8000
新时代前端农民工应该怎么准备面试(二)
【tauri开发】windows管理员身份开机启动
最近遇到个bug,问题一路捋上来,查到了windows权限相关,即我需要将应用改成默认使用管理员身份运行
微芒不朽
2024/06/25
4360
【tauri开发】windows管理员身份开机启动
前端架构思考,Vue or React?领域设计、文件结构、数据管理、主题替换
其实写到这里,相信大家已经明白我的价值倾向了。在没有企业包袱的角度来看,大厂都是 react 为先😯, 我更加推荐使用 vue,原因如下👇
winty
2023/10/26
3670
前端架构思考,Vue or React?领域设计、文件结构、数据管理、主题替换
用 console 画条龙?
console一定是各位前端er最熟悉的小伙伴了,无论是console控制台,还是console对象,做前端做久了,打开一个网页总是莫名自然的顺手打开控制台,有些调皮的网站还会故意在控制台输出一些有意思的东西,比如招聘信息,像百度的:
winty
2021/07/27
8590
相关推荐
浏览器指纹解读
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档