簡介
Protocol Buffers(簡稱Protobuf)是由Google
開發的一種用於資料序列化技術。與傳統的XML
和JSON
相比,Protobuf
具有更高的效能和更小的訊息體積,特別適用於需要高效資料交換的場景
特點
- 速度快:
Protobuf
在序列化與反序列化資料時速度極快 - 佔空間小:
Protobuf
序列化後的二進位制資料非常小,可節省大量的儲存和頻寬 - 跨平臺:
Protobuf
支援多種程式語言(常見的幾乎都支援),相容性好 - 易擴充套件:使用
.proto
檔案定義資料結構(包括欄位型別、預設值和驗證規則等),新增新內容時,也可以輕鬆做到,不會破壞現有系統 - 簡單易用:只需專注
.proto
資料結構檔案的編寫,對應的序列化與反序列化程式碼可自動生成
使用場景
- 分散式系統:各服務之間需要頻繁地進行資料交換,
Protobuf
可以顯著提高通訊效率 - 儲存和持久化:
Protobuf
能節省很多儲存空間,常用於日誌記錄、配置檔案和資料持久化 - 移動應用:在網路頻寬和儲存空間有限的情況下,
Protobuf
能更好地提高效能
Proto
proto是protobuf定義資料結構的一種格式,用.proto
字尾的檔案儲存,使用proto編譯器可以把對應的proto
結構編譯為各目標語言的序列化與反序列化程式碼庫
簡單的使用模擬
-
使用
proto
語法(規則)定義我們的資料結構,儲存在.proto
字尾的檔案內 -
使用proto編譯器編譯
.proto
檔案,指定要編譯成目標語言環境(Java
、Python
等)- 結果將會生成對應語言的一個程式碼檔案(內包含對應資料序列化與反序列化相關操作的類或函式)
-
接著使用生成的程式碼檔案即可
Proto語法
首先,先看一則訊息結構,如下:
syntax = "proto3";
message Student {
string id = 1;
string name = 2;
int32 age = 3;
}
proto3
表示使用的proto
版本,如不如上指明,編譯器會預設為proto2
message Student
表示定義一個名為Student
的訊息結構{}
內為訊息的欄位,採用型別 欄位名 = 編號;
的形式描述
欄位型別
proto
的型別主要有三類:標量型別、列舉或複合型別(如其它訊息型別)
標量型別
常用的標量型別如下:
proto型別 | 註釋 | 對應C++型別 |
---|---|---|
double | 雙精度浮點數 | double |
float | 單精度浮點數 | float |
int32 | 使用可變長度編碼,對負數編碼效率不高 - 如果欄位可能具有負值,可使用 sint32 | int32 |
int64 | 使用可變長度編碼,對負數編碼效率不高 - 如果欄位可能具有負值,可使用 sint64 | int64 |
uint32 | 使用可變長度編碼 | uint32 |
uint64 | 使用可變長度編碼 | uint64 |
sint32 | 使用可變長度編碼,有符號 int 值,與常規 int32 相比,可更有效地對負數進行編碼 | int32 |
sint64 | 使用可變長度編碼,有符號 int 值,與常規 int64 相比,可更有效地對負數進行編碼 | int64 |
fixed32 | 始終為四個位元組,如果值通常大於 228,則比 uint32 更高效 | uint32 |
fixed64 | 始終為八個位元組,如果值通常大於 256,則比 uint64 更高效 | uint64 |
sfixed32 | 始終為四個位元組 | int32 |
sfixed64 | 始終為八個位元組 | int64 |
bool | 布林值 | bool |
string | 字串必須始終包含 UTF-8 編碼或 7 位 ASCII 文字,並且不能長於 232 | string |
bytes | 可能包含不長於 232 的任何任意位元組序列 | string |
上表值給出了C++
中對應的型別參考,其它語言亦有與proto
對應的型別,具體可自行搜查
預設值:當解析訊息時,如果編碼的訊息不包含某些元素,則訪問解析物件中的相應欄位將返回該欄位的預設值,預設值相關於型別:
- 對於字串,預設值為空字串
- 對於位元組,預設值為空位元組
- 對於布林值,預設值是
false
- 對於數字型別,預設值是
0
- 對於列舉,預設值是第一個定義的列舉值,它必須是
0
列舉
定義一個型別,值為預定義值列表中的一個值
如下,是一個列舉參考:
enum Week {
MONDAY = 0;
TUESDAY = 1;
WEDNESDAY = 2;
THURSDAY = 3;
FRIDAY = 4;
SATURDAY = 5;
SUNDAY = 6;
}
- 每個列舉都需要一個常量(32位整數範圍內)
- 第一個需要為零
保留值:定義完一個列舉後,之後某個時間點可能由於業務需要,要修改其結構,或刪除一些項,如果僅是註釋掉或直接刪除,會導致列舉的常量值不連續,後續其它開發者可以使用這些值新增其它列舉項,如果他們後續又載入相同
.proto
的舊版本,可能會導致嚴重的問題(如資料損壞等)為了防止這種問題發生,我們可以用
reserved
將這些刪除項的常量值宣告為保留值,防止後續開發者使用
enum Test {
reserved 3, 5, 7 to 12, 37 to max;
reserved "FIR", "CAR";
}
-
reserved
後跟保留內容 -
數字表示保留常量
-
字串表示保留欄位名稱
-
to
用於連線連續的一段值 -
max
表示指定常量數字範圍的最大可能值 -
同一個
reserved
語句中不能同時混合欄位名稱和數字值
其它訊息型別
表示用message
定義的其它訊息型別
巢狀訊息
訊息除了一個個定義,還可巢狀定義,如下:
message UserInfo {
message Avatar {
string url = 1;
string date = 2;
}
string nickname = 1;
string sign = 2;
Avatar avatar = 3;
}
- 在訊息內部定義的訊息,即為巢狀訊息,內部可透過其名稱直接使用
- 外部如想使用巢狀訊息,可透過
_Parent_._Type_
的形式引用,如:UserInfo.Avatar
如果想用其它
.proto
檔案內定義的型別,可在檔案頭使用import
檔案路徑匯入指定檔案使用
對映
使用關鍵字map
建立、宣告,配對鍵/值欄位型別
形式為:map<key, value> name = N;
key
可以是數字或字串型別(即除了浮點數和 bytes 外的所有標量型別)value
可以是任何型別,處理另一個map
舉例一個對映如下
message Request {
string url = 1;
map<string, string> headers = 2;
}
欄位編號
定義訊息欄位時,必須給每個欄位都分配一個編號,數值在1
到536,870,911
之間
- 給定的數值必須在該訊息的所有欄位中唯一
- 編號
19,000
至19,999
為Protobuf
實現保留,不可用 - 不可使用
reserved
定義的保留值
建議使用
1
到15
的數字設定常用欄位,因為它們只需一個位元組進行編碼,16
之後的數字至少需要兩個位元組
欄位標籤
用於宣告在定義欄位的型別前,起到某種作用
-
required
:表示欄位為必填欄位,建立訊息時,必須設定該欄位的值 -
optional
:表示欄位是可選欄位,建立訊息時,可設定值也可不設定,訊息接收方如可識別,則對應處理,如不可識別則忽略 -
repeated
:表示該欄位可重複0~N
個,保留重複值的順序,即相當於一個陣列、列表
保留欄位
與上方列舉提到的用法型別,透過reserved
定義保留編號和保留欄位名稱
註釋
proto
採用與C/C++
同樣式的註釋方法://
或/* ... */
包
為了防止協議訊息型別之間的名稱衝突,可以在.proto
檔案中新增可選的package
說明符
package foo;
message Test { ... }
定義訊息型別的欄位時可帶上包說明符foo.Test bar = 1;
在編譯為不同語言時,會做不同處理,如:對於java解析為java中的包,對於C++則解析為名稱空間
後言
protobuf
使用非常廣泛,如很多平臺的影片、直播間彈幕流即是使用這種技術傳輸,如有需要,還可對序列化訊息進行gzip
壓縮,進一步減少訊息體積,節省頻寬,提高傳輸速度