【1】深入学习 Protocol Buffers(Protobuf)
本文将向大家展示如何定义一台完整的笔记本电脑模型,从而深入学习Protocol Buffers
。
目录结构:
laptop
├── proto
│ └── xxx_message.proto
└── pb
└── xxx_message.pb.go
一、1个文件中包含多条消息
让我们从processor_message.proto
文件开始。我们可以在1个文件中定义多个消息,因此我将在此处添加GPU消息。这是有道理的,因为GPU也是处理器。
syntax="proto3";
package laptop.pbfiles;
option go_package=".;pb";
message CPU {
string brand = 1; // 品牌,如:AMD
string name = 2; // 型号:如:Ryzen 7 3700X
uint32 number_cores = 3; // 核心
uint32 number_threads = 4; // 线程
double min_ghz = 5; // 最低频率
double max_ghz = 6; // 最高频率
}
message GPU {
string brand = 1;
string name = 2;
double min_ghz = 3;
double max_ghz = 4;
// memory ?
}
GPU,即显卡,它具有与CPU类似的字段,例如品牌,名称,最小和最大频率。唯一不同的是它具有自己的内存。
所以,这个memory
该如何定义?且慢,待我一一道来,继续往下看。
二、自定义类型:消息和枚举
内存是一个非常流行的术语,可以在其他地方使用,例如RAM(随机存取存储器)或ROM(硬盘)。它具有许多不同的度量单位,例如千字节,兆字节,千兆字节或太字节。因此,我将在一个单独的memory_message.proto
文件中将其定义为自定义类型,以便以后使用。
首先,我们需要定义度量单位,即多少M,多少G。为此,我们将使用枚举。这里将度量单位命名为Unit
。因为此Unit
仅应存在于内存的上下文中,所以我们应将其定义为Memory消息内的嵌套类型。
syntax="proto3";
package laptop.pbfiles;
option go_package=".;pb";
message Memory {
enum Unit {
UNKNOWN = 0;
BIT = 1;
BYTE = 2;
KILOBYTE = 3; // 千字节
MEGABATE = 4; // 百万字节
GIGABYTE = 5; // 十亿字节
TERABYTE = 6; // 万亿字节
}
uint64 value = 1;
Unit unit = 2;
}
当使用enum
类型时,惯例是,总是使用一个特殊的值作为枚举的默认值,并为它分配标签0。然后我们添加其他单位,从BIT到TERABYTE。
内存消息将有两个字段:一个用于值,另一个用于指定内存大小的类型。比如,value为120,unit为GIGABYTE,即表示内存大小为120G。
定义好Memory
消息后,即可回到processor_message.proto
,将其导入使用。
import "memory_message.proto";
message GPU {
...
Memory memory = 5;
}
三、定义存储消息
我们要为storage_message.proto
文件中的Storage
创建一条新消息。
存储器可以是机械硬盘或固态硬盘。因此,我们应该使用这两个值定义一个Driver
枚举。
syntax="proto3";
package laptop.pbfiles;
option go_package=".;pb";
import "memory_message.proto";
message Storage {
enum Driver {
UNKNOWN = 0;
HDD = 1;
SSD = 2;
}
Driver driver = 1; // 硬盘类型
Memory memory = 2; // 硬盘大小
}
四、定义键盘信息
接下来,我们在keyboard_message.proto
中定义键盘消息类型。它可以具有QWERTY,QWERTZ或AZERTY布局。供您参考,QWERTY在中国可能是唯一选择。QWERTZ在德国已广泛使用。在法国,AZERTY更受欢迎。
syntax="proto3";
package laptop.pbfiles;
option go_package=".;pb";
message Keyboard {
enum Layout {
UNKNOWN = 0;
QWERTY = 1;
QWERTZ = 2;
AZERTY = 3;
}
Layout layout = 1; // 键盘布局
bool backlit = 2; // 是否是发光键盘
}
键盘可以是背光的,也可以不是背光的,因此我们为其使用布尔值字段。很简单,对吧?
五、定义屏幕消息
现在让我们在screen_message.proto
中写一个更复杂的消息:屏幕。它具有嵌套的消息类型:Resolution
。我们在这里使用嵌套类型的原因是:分辨率是一个与屏幕紧密联系的实体,单独显示时没有任何意义。
syntax="proto3";
package laptop.pbfiles;
option go_package=".;pb";
message Screen {
message Resolution {
uint32 width = 1;
uint32 height = 2;
}
enum Panel {
UNKNOWN = 0;
IPS = 1;
OLED = 2;
}
float size_inch = 1; // 屏幕尺寸
Resolution resolution = 2; // 屏幕分辨率
Panel panel = 3; // 屏幕类型
bool multitouch = 4; // 是否支持多点触控
}
同样,我们有一个用于屏幕类型的枚举,可以是IPS或OLED。然后屏幕尺寸以英寸为单位。最后是bool字段,用于判断它是否为多点触摸屏。
六、定义笔记本电脑消息
好吧,我认为基本上我们已经定义了笔记本电脑的所有必要组件。因此,让我们现在laptop_message.proto
中定义笔记本电脑消息。
message Laptop {
string id = 1;
string brand = 2;
string name = 3;
CPU cpu = 4; // cpu
Memory ram = 5; // 内存
}
它具有字符串类型的唯一标识符。该ID将由服务器自动生成。它有一个品牌和名称。然后是CPU和RAM。
Repeated 字段
一台笔记本电脑可以拥有1个以上的GPU,因此我们使用repeated
关键字告诉protoc这是GPU的列表。
同样,一台笔记本电脑具有多个存储空间是正常的,因此也应重复此字段。
message Laptop {
repeated GPU gpus = 6; // 显卡(可以有多块)
repeated Storage storages = 7; // 存储盘(可以有多块)
Screen screen = 8;
Keyboard keyboard = 9;
}
然后是2个普通字段:屏幕和键盘。这很简单。
oneof 字段
笔记本电脑的重量该如何定义?假设我们允许以千克或磅指定。为此,我们可以使用一个新关键字:oneof
。
message Laptop {
oneof weight { // 重量(千克或者磅表示)
double weight_kg = 10;
double weight_lb = 11;
}
}
在oneof
中,我们定义2个字段,一个字段表示千克,另一个字段表示磅。请记住,当您使用oneof
字段组时,只有最后分配的字段会保留其值。
导入其他知名消息类型
然后,我们再添加2个字段:价格和笔记本电脑的发行年份。最后,我们需要一个时间戳字段以将记录的最后更新时间存储在我们的系统中。
message Laptop {
double price = 12;
uint32 release_year = 13; // 上市年份
google.protobuf.Timestamp updated_at = 14;
}
还有许多其他众所周知的类型。请查看此链接以了解有关它们的更多信息。
完整的笔记本电脑消息:
syntax="proto3";
package laptop.pbfiles;
option go_package=".;pb";
import "processor_message.proto";
import "memory_message.proto";
import "storage_message.proto";
import "screen_message.proto";
import "keyboard_message.proto";
import "google/protobuf/timestamp.proto";
message Laptop {
string id = 1;
string brand = 2;
string name = 3;
CPU cpu = 4;
Memory ram = 5;
repeated GPU gpus = 6;
repeated Storage storages = 7;
Screen screen = 8;
Keyboard keyboard = 9;
oneof weight {
double weight_kg = 10;
double weight_lb = 11;
}
double price = 12;
uint32 release_year = 13;
google.protobuf.Timestamp updated_at = 14;
}
现在我们可以执行protoc
命令为所有定义好的 proto 消息生成Go代码了。
目录结构:
laptop
├── proto
│ ├── processor_message.proto
│ ├── memory_message.proto
│ ├── storage_message.proto
│ ├── keyboard_message.proto
│ ├── screen_message.proto
│ └── laptop_message.proto
├── pb
│ ├── processor_message.pb.go
│ ├── memory_message.pb.go
│ └── storage_message.pb.go
│ ├── keyboard_message.pb.go
│ ├── screen_message.pb.go
│ └── laptop_message.pb.go
└── Makefile
为了方便,使用Makefile
管理protoc
命令。
gen:
protoc --proto_path=proto proto/*.proto --go_out=plugins=grpc:pb
OK,下一部分,我将对比protobuf
与json
,以了解为什么protobuf
比json
快。