Protocol Buffers 入門詳解

Protocol Buffers 入門詳解

1. 概念

1.1 What?(什麼是Protocol Buffers?)

  Protocol Buffers(後面簡稱protobuf)是google團隊開發的一種語言中立,平臺無關,可擴展的數據壓縮編碼方式(序列化),其很適合作數據存儲或RPC數據交換格式。可用於通信協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。目前提供了 C++、Java、Python 三種語言的 API。ide

1.2 Why?(爲何使用Protocol Buffers?)

  Protobuf的存在有兩個緣由,一個是爲了從數據存儲的角度來加速站點間數據傳輸速度,另外一個是爲了解決站點間數據傳輸的協議不規範問題。
傳輸數據的大小無疑是影響傳輸速度的關鍵因素,當前流行的數據傳輸協議(json、xml等)會攜帶一些「結構化」的數據(如標籤等),另外,它們對數據的壓縮也沒有很極致,因此須要有一個對數據存儲很「緊湊」,對數據壓縮很高效的工具來進行革新。工具

  最初的數據傳輸協議是request/response形式的,沒有 protocol buffers 以前,google 已經存在了一種 request/response 格式,用於手動處理 request/response 的編碼和反編碼。可是這種協議每每沒有很明確的格式,因此開發人員常常會遇到新舊版協議不兼容的問題。所以,急需一個協議不須要了解全部業務字段還能靈活地應對各類改動的需求。性能

1.3 How?(Protocol Buffers 是怎麼作到的?)

  protobuf對傳輸的數據採起一種最簡單的key-value形式的存儲方式(但其中有一種類型的數據不是k-v形式,後面會講),這鐘存儲方式極大的節省了空間。除此以外protobuf還採起了varint(變長編碼)形式來壓縮數據,對體積較小的字段分配較少的空間,由此使得壓縮後的文件很是「緊湊」。

  protobuf定義了一種後綴名爲「.proto」的描述型文件爲待傳輸的結構化數據做爲數據協議,待傳輸的數據必須符合「.proto」文件中的相關定義。「.proto」文件在簡潔易讀的同時很好地保留了原數據的結構信息,而且還給出了一些實用的關鍵字,靈活地讓開發者對數據中的字段作選取,給了開發人員很大的發揮空間,基本解決了其餘協議出現的被需求牽着走的局面。「.proto」文件能夠看做一種數據傳輸的協議,須要開發人員按照語法編寫,其還能夠經過protobuf工具編譯成C++/Java/Python對象。

2.protobuf文件中的語法規範

2.1 message結構

  在「.proto」文件中protobuf將一種結構稱爲一個message,相似Java中的一個Entity對象,咱們以電話簿中的數據爲例:
avatar
  其中Person表明這個結構體的名字,name、id、email都是結構體中的字段,咱們稱之爲field。要注意的是,等號右邊賦值的不是value,而是字段field對應的field id。Field最前面的required、optional、repeated是這個Filed的規則,分別表示該數據結構中這個Filed有且只有1個,能夠是0個或1個,能夠是0個或任意個。optional後面能夠加default默認值,若是不加,數據類型的默認爲0,字符串類型的默認爲空串。repeated後面加[packed=true]會使用新的更高效的編碼方式。

2.2 enum類型

  其意義和Java中的enum一致,在 message 中能夠嵌入枚舉類型

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}

  枚舉類型須要注意的是,必定要有 0 值。

  枚舉爲 0 的是做爲零值,當不賦值的時候,就會是零值。

  爲了和 proto2 兼容。在 proto2 中,零值必須是第一個值。

2.3 Service接口

  果要使用 RPC(遠程過程調用)系統的消息類型,能夠在 .proto 文件中定義 RPC 服務接口,protocol buffer編譯器將使用所選語言生成服務接口代碼和stubs。因此,例如,若是你定義一個 RPC 服務,入參是 SearchRequest 返回值是 SearchResponse,你能夠在你的 .proto 文件中定義它,以下所示:

service SearchService {
  rpc Search (SearchRequest) returns (SearchResponse);
}

2.4 命名規範

  message、enum、Service都採用駝峯命名法。即首字母大寫開頭,其中message和enum中的字段名採用下劃線分隔法命名。示例以下:

message SongServerRequest {
  required string song_name = 1;
}

enum Foo {
  FIRST_VALUE = 0;
  SECOND_VALUE = 1;
}

service FooService {
  rpc GetSomething(FooRequest) returns (FooResponse);
}

2.5 特殊狀況

2.5.1 爲已棄用的field保存field id

  以前說過,每一個field對應惟一一個field id,可是在工做中可能會出現這麼一種狀況:「即在項目中某field被棄用,以後的版本再也不使用該field了,可是仍有一部分用戶使用以前的開發版本,此時若徹底將對應的field id不當心應用到新的field中,必定會引發混亂」,因此此時應該將一些field id設置爲保留類型,即不能被用來定義新的field,若是之後有人使用,protobuf編譯器會提出出錯。示例以下:

message Person {
  reserved 2, 15, 9 to 11;   #9 to 11表示九、十、11三個field id
  reserved "samples", "email";
}

注意一個reserved字段不能既有標籤數字又有名字

3. Protobuf存儲原理和壓縮原理

3.1 存儲原理

  protobuf對不一樣對數據類型進行分類,分別爲其選擇不一樣對存儲格式,不過多數數據對存儲格式都是鍵值對的key-value形式。protobuf將全部數據類型分爲了一下幾類:

type meaning used for
0 varint int32,int64,uint32,uint64,sint32,sint64,bool,enum
1 64-bit fixed64,sfixed64,double
2 Length-delimited string,bytes,embedded message,packed repeated fields
5 32-bit fixed32,sfixed32,float

  其中類型三、4已經在proto3中被棄用,故不列出。除了類型2的數據以外的三個類型所有是由k-v形式存儲,類型2數據因爲長度不定,因此在存儲的時候還須要加入一個length指標,以k-l-v的形式存儲。

  對於message這種結構體數據,protobuf把message中全部字段的數據表示成k-v/k-l-v形式後拼接在一塊兒,減小分隔符所佔用的空間。這樣一來有個問題出現了,那就是如何在一長串的拼接起來的二進制數據中找到對應的field?,而且還要肯定該field是k-v存儲仍是k-l-v存儲的呢?

  這個問題的答案在存儲field的「key」中,「key」由兩個值決定,分別是field id和該field的類型編號,其計算方法是key = (field_id << 3) | type,其中|表明兩個二進制類型的拼接,示例以下圖所示:
avatar
  要注意的是,key在某些時地方也會被稱做tag。

3.1.1 varint類型存儲(type=0)

  這裏須要特別說明一下的是,在type=0的數據類型中,除了正數整型外還存在負數整型,一個負數整型通常會被表示爲一個很大的整數,由於計算機定義負數的符號位爲數字的最高位。若是採用Varints這種編碼方式表示一個負數,那麼必定須要 10 個byte長度。

  然而protobuf定義了一種新的sint32/64類型,其採用ZigZag編碼方式,首先將全部整數映射成無符號整數(即-1 將會被編碼成 1,1 將會被編碼成 2,-2 會被編碼成 3,以此類推...),而後再採用Varint編碼方式編碼。這樣一來絕對值小的整數,編碼後也會有一個較小的Varint編碼值,無縫對接。

3.1.2 32/64-bit類型存儲(type=一、type=5)

  &type=1時,在解析時告訴解析器,該類型的數據須要一個 64 位大小的數據塊便可。
  type=5時,float 和 fixed32 的 wire_type 爲5,給其 32 位數據塊便可。

3.1.2 Length-delimited類型存儲(type=2)

  type=2時,field以k-l-v格式存儲,如今假設一個message中有一個string類型的field,名字爲b,以下所示:

message Test2 {
  optional string b = 2;
}

  如今咱們假設b被賦上的值爲「testing」,7個英文字符,每一個字符分別對應的十六進制碼爲「74 65 73 74 69 6e 67」。

  以字符t爲例,其二進制表示爲0111 0100,將其右三位後爲0000 1110,再與type=2的二進制0000 0010作「或」運算獲得t字符最後的key/tag爲0000 1110

  肯定了key/tag以後,接着是length,其爲7——>「0000 0111」。length肯定後接着跟value,即「testing」的7個字符的二進制表示。由此一個字符串類型的protobuf的k-l-v存儲就結束了。

3.2 壓縮原理

  protobuf採起的壓縮算法是Varints,Varint是一種緊湊的、不定長的表示數字的算法,它用一個或多個字節來表示一個數字,其中值越小的數字使用越少的字節數,這樣能夠節省表示較小數字的空間。

4. Protobuf的特色

4.1 優勢

  操做性簡單:無複雜的文檔對象模型,自動生成類,語義清晰

  安全性強:沒有得到.proto文件就沒法破解

  兼容性強:具備向後兼容的特性,更新數據結構之後,老版本依舊能夠兼容跨語言:支持C++、Python、Java

  壓縮性能好:壓縮後的文件體積小(因爲varint壓縮和option等關鍵字的緣由,也不像json、xml同樣有<>等符號)

4.2 缺點

  可讀性差:在沒有.proto文件的基礎下沒法人爲讀懂傳遞過來的數據

  表達能力差:沒法描述數據結構,沒法對標記文檔(html等)建模

5. 與其餘序列化協議比較

   protobuf JSON XML Lua
數據結構支持 較複雜結構 簡單結構 複雜結構 複雜結構
數據存儲方式 二進制 文本 文本 文本
數據壓縮大小 通常 通常
編碼解碼效率 通常 稍快
編程語言支持 C++/Python/Java 不少 不少 不少
開發難度 簡單 簡單 繁瑣 相對繁瑣
學習成本
適用範圍 數據交換 數據交換 數據交換 數據保存及腳本處理
相關文章
相關標籤/搜索