1. 程式人生 > 實用技巧 >【1】深入學習 Protocol Buffers(Protobuf)

【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,下一部分,我將對比protobufjson,以瞭解為什麼protobufjson快。