Protobuf 有如 XML,不過它更小、更快、也更簡單。你能夠定義本身的數據結構,而後使用代碼生成器生成的代碼來讀寫這個數據結構。你甚至能夠在無需從新部署程序的狀況下更新數據結構。只需使用 Protobuf 對數據結構進行一次描述,便可利用各類不一樣語言或從各類不一樣數據流中對你的結構化數據輕鬆讀寫。linux
它有一個很是棒的特性,即「向後」兼容性好,人們沒必要破壞已部署的、依靠「老」數據格式的程序就能夠對數據結構進行升級。這樣您的程序就能夠沒必要擔憂由於消息結構的改變而形成的大規模的代碼重構或者遷移的問題。由於添加新的消息中的 field 並不會引發已經發布的程序的任何改變。程序員
Protobuf 語義更清晰,無需相似 XML 解析器的東西(由於 Protobuf 編譯器會將 .proto 文件編譯生成對應的數據訪問類以對 Protobuf 數據進行序列化、反序列化操做)。數據庫
使用 Protobuf 無需學習複雜的文檔對象模型,Protobuf 的編程模式比較友好,簡單易學,同時它擁有良好的文檔和示例,對於喜歡簡單事物的人們而言,Protobuf 比其餘的技術更加有吸引力。編程
Protobuf 與 XML 相比也有不足之處。它功能簡單,沒法用來表示複雜的概念。網絡
XML 已經成爲多種行業標準的編寫工具,Protobuf 只是 Google 公司內部使用的工具,在通用性上還差不少。數據結構
因爲文本並不適合用來描述數據結構,因此 Protobuf 也不適合用來對基於文本的標記文檔(如 HTML)建模。另外,因爲 XML 具備某種程度上的自解釋性,它能夠被人直接讀取編輯,在這一點上 Protobuf 不行,它以二進制的方式存儲,除非你有 .proto 定義,不然你無法直接讀出 Protobuf 的任何內容。工具
到這裏爲止,咱們只給出了一個簡單的沒有任何用處的例子。在實際應用中,人們每每須要定義更加複雜的 Message。咱們用「複雜」這個詞,不只僅是指從個數上說有更多的 fields 或者更多類型的 fields,而是指更加複雜的數據結構:性能
嵌套 Message學習
嵌套是一個神奇的概念,一旦擁有嵌套能力,消息的表達能力就會很是強大。ui
message Person { required string name = 1; required int32 id = 2; // Unique ID number for this person. optional string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME]; } repeated PhoneNumber phone = 4; }
在 Message Person 中,定義了嵌套消息 PhoneNumber,並用來定義 Person 消息中的 phone 域。這使得人們能夠定義更加複雜的數據結構。
Import Message
在一個 .proto 文件中,還能夠用 Import 關鍵字引入在其餘 .proto 文件中定義的消息,這能夠稱作 Import Message,或者 Dependency Message。好比下例:
import common.header; message youMsg{ required common.info_header header = 1; required string youPrivateData = 2; }
其中,
common.info_header定義在
common.header包內。
Import Message 的用處主要在於提供了方便的代碼管理機制。您能夠將一些公用的 Message 定義在一個 package 中,而後在別的 .proto 文件中引入該 package,進而使用其中的消息定義。Google Protocol Buffer 能夠很好地支持嵌套 Message 和引入 Message,從而讓定義複雜的數據結構的工做變得很是輕鬆愉快。
人們一直在強調,同 XML 相比, Protobuf 的主要優勢在於性能高。它以高效的二進制方式存儲,比 XML 小 3 到 10 倍,快 20 到 100 倍。對於這些 「小 3 到 10 倍」,「快 20 到 100 倍」的說法,嚴肅的程序員須要一個解釋。所以在本文的最後,讓咱們稍微深刻 Protobuf 的內部實現吧。
有兩項技術保證了採用 Protobuf 的程序能得到相對於 XML 極大的性能提升。
第一點,咱們能夠考察 Protobuf 序列化後的信息內容。您能夠看到 Protocol Buffer 信息的表示很是緊湊,這意味着消息的體積減小,天然須要更少的資源。好比網絡上傳輸的字節數更少,須要的 IO 更少等,從而提升性能。
第二點,咱們須要理解 Protobuf 封解包的大體過程,從而理解爲何會比 XML 快不少。
Protobuf 序列化後所生成的二進制消息很是緊湊,這得益於 Protobuf 採用的很是巧妙的 Encoding 方法。考察消息結構以前,讓我首先要介紹一個叫作 Varint 的術語。Varint 是一種緊湊的表示數字的方法。它用一個或多個字節來表示一個數字,值越小的數字使用越少的字節數。這能減小用來表示數字的字節數。好比對於 int32 類型的數字,通常須要 4 個 byte 來表示。可是採用 Varint,對於很小的 int32 類型的數字,則能夠用 1 個 byte 來表示。固然凡事都有好的也有很差的一面,採用 Varint 表示法,大的數字則須要 5 個 byte 來表示。從統計的角度來講,通常不會全部的消息中的數字都是大數,所以大多數狀況下,採用 Varint 後,能夠用更少的字節數來表示數字信息。下面就詳細介紹一下 Varint。
Varint 中的每一個 byte 的最高位 bit 有特殊的含義,若是該位爲 1,表示後續的 byte 也是該數字的一部分,若是該位爲 0,則結束。其餘的 7 個 bit 都用來表示數字。所以小於 128 的數字均可以用一個 byte 表示。大於 128 的數字,好比 300,會用兩個字節來表示:1010 1100 0000 0010
下圖演示了 Google Protocol Buffer 如何解析兩個 bytes。注意到最終計算前將兩個 byte 的位置相互交換過一次,這是由於 Google Protocol Buffer 字節序採用 little-endian 的方式。
消息通過序列化後會成爲一個二進制數據流,該流中的數據爲一系列的 Key-Value 對。以下圖所示:
採用這種 Key-Pair 結構無需使用分隔符來分割不一樣的 Field。對於可選的 Field,若是消息中不存在該 field,那麼在最終的 Message Buffer 中就沒有該 field,這些特性都有助於節約消息自己的大小。
Key 用來標識具體的 field,在解包的時候,Protocol Buffer 根據 Key 就能夠知道相應的 Value 應該對應於消息中的哪個 field。Key 的定義以下:
(field_number << 3) | wire_type
能夠看到 Key 由兩部分組成。第一部分是 field_number,即爲標識數字id。第二部分爲 wire_type。表示 Value 的傳輸類型。Wire Type 可能的類型以下表所示:
細心的讀者或許會看到在 Type 0 所能表示的數據類型中有 int32 和 sint32 這兩個很是相似的數據類型。Google Protocol Buffer 區別它們的主要意圖也是爲了減小 encoding 後的字節數。在計算機內,一個負數通常會被表示爲一個很大的整數,由於計算機定義負數的符號位爲數字的最高位。若是採用 Varint 表示一個負數,那麼必定須要 5 個 byte。爲此 Google Protocol Buffer 定義了 sint32 這種類型,採用 zigzag 編碼。Zigzag 編碼用無符號數來表示有符號數字,正數和負數交錯,這就是 zigzag 這個詞的含義了。如圖所示:
使用 zigzag 編碼,絕對值小的數字,不管正負均可以採用較少的 byte 來表示,充分利用了 Varint 這種技術。其餘的數據類型,好比字符串等則採用相似數據庫中的 varchar 的表示方法,即用一個 varint 表示長度,而後將其他部分緊跟在這個長度部分以後便可。