序列化和反序列化##
序列化和反序列化在平常工作中會大量使用,然而並不一定非常清楚它的概念。序列化和反序列化的選型卻是系統設計或重構一個重要的環節,在分布式、大數據量系統設計里面更為顯著。機器間的通信需要約定一個協議,序列化和反序列化是這個通信協議的一部分。
序列化
:將對象或數據結構轉為字節序列的過程。
反序列化
:將序列化后生成的字節序列轉為對象或數據結構的過程。
常用序列化和反序列化組件##
比較常見的序列化和反序列化組方式有XML、JSON和Protobuf等。XML標准化較早,基於XML的SOAP是一種應用廣泛的結構化數據傳遞協議。JSON源於js,較之XML它更小、解析更快,而且同樣具備可讀性好的優點。而源於谷歌的protobuf現在在大型分布式系統廣泛使用。
Protobuf##
Protobuf是谷歌推出的一款平台無關,語言無關,可擴展的序列化和反序列化技術。
字段規則
要使用Protobuf,首先需要定義.proto文件
如下proto2
中:
message msg
{
required int32 a=1;
optional string b=2;
repeated string c=3;
}
其中:
- message是消息定義的關鍵字。
- required 表示這個字段必須的,必須在序列化的時候被賦值。
- optional 代表這個字段是可選的,可以為0個或1個但不能大於1個。
- repeated 則代表此字段可以被重復任意多次包括0次。類似C++ STL中的vector。
- int32和string是字段的類型。后面是我們定義的字段名。
- 最后的1,2,3則是代表每個字段的一個唯一的編號標簽,在同一個消息里不可以重復。這些編號標簽用與在消息二進制格式中標識你的字段,並且消息一旦定義就不能更改。需要說明的是標簽在1到15范圍的采用一個字節進行編碼。所以通常將標簽1到15用於頻繁發生的消息字段。編號標簽大小的范圍是1到\(2^{29}-1\)。此外不能使用protobuf系統預留的編號標簽(19000 -19999)。
而在proto3
中,字段規則中去除了required和optional,增加singular。但是proto3仍兼容proto2
。
message msg
{
int32 a=1;
singular string b=2;
repeated string c=3;
}
其中,
- singular:一個格式良好的消息應該有0個或者1個這種字段(但是不能超過1個)。
- repeated
- 在proto3中,repeated的標量域默認情況下使用packed。
一個較完整的.proto文件
syntax = "proto3";
message Article {
int32 article_id = 1;
singular string article_excerpt = 2;
repeated string article_picture = 3;
singular int32 article_pagecount = 4 [default = 0];
enum ArticleType {
NOVEL = 0;
PROSE = 1;
PAPER = 2;
POETRY = 3;
}
singular ArticleType article_type = 5 [default = NOVEL];
message Author {
string name = 1;
singular string phone = 2;
}
singular Author author = 6;
repeated int32 article_numberofwords = 7 [packed=true];
reserved 9, 10, 12 to 15;
extensions 100 to 1000;
}
extend Article {
singular int32 followers_count = 101;
singular int32 likes_count= 102;
}
message Other {
singular string other_info = 1;
oneof test_oneof {
string code1 = 2;
string code2 = 3;
}
}
- 上面proto文件,我們定義了enum枚舉類型,嵌套的消息。甚至對原有的消息進行了擴展,也可以對字段設置默認值。添加注釋等,類似C++注釋。
- 此外reserved關鍵字主要用於保留相關編號標簽,主要是防止在更新proto文件刪除了某些字段,而未來的使用者定義新的字段時重新使用了該編號標簽。這會引起一些問題在獲取老版本的消息時,譬如數據沖突,隱藏的一些bug等。所以一定要用reserved標記這些編號標簽以保證不會被使用。
- 當我們需要對消息進行擴展的時候,我們可以用extensions關鍵字來定義一些編號標簽供第三方擴展。這樣的好處是不需要修改原來的消息格式。就像上面proto文件,我們用extend關鍵字來擴展。只要擴展的字段編號標簽在extensions定義的范圍里。
- 對於基本數值類型,由於歷史原因,不能被protobuf更有效的encode。所以在新的代碼中使用packed=true可以更加有效率的encode。注意packed只能用於repeated 數值類型的字段。不能用於string類型的字段。
- 在消息Other中我們看到定義了一個oneof關鍵字。這個關鍵字作用比較有意思。當你設置了oneof里某個成員值時,它會自動清除掉oneof里的其他成員,也就是說同一時刻oneof里只有一個成員有效。這常用於你有許多optional字段時但同一時刻只能使用其中一個,就可以用oneof來加強這種效果。但需要注意的是oneof里的字段不能用singular,repeated關鍵字。
導入定義
我們總不能都定義在一個文件中。當一個proto文件需要另一個proto文件的時候,我們可以通過import導入。protobuf也提供了包的定義,只要在文件開頭定義package關鍵字即可。
import "test.proto"
package foo.bar;
編譯問題
針對不同語言,依據.proto文件編譯成我們需要的語言文件。如C++下
protoc -I=SRC_DIR --cpp_out=DST_DIR SRC_DIR/ex.proto
先簡單記錄這些。