天天看點

深入 ProtoBuf - 簡介

之前在網絡通信和通用資料交換等應用場景中經常使用的技術是 json 或 xml,而在最近的開發中接觸到了 google 的 protobuf。

在查閱相關資料學習 protobuf 以及研讀其源碼之後,發現其在效率、相容性等方面非常出色。在以後的項目技術選型中,尤其是網絡通信、通用資料交換等場景應該會優先選擇 protobuf。

自己在學習 protobuf 的過程中翻譯了官方的主要文檔,一來當然是在學習 protobuf,二來是培養閱讀英文文檔的能力,三來是因為 google 的文檔?不存在的!

看完這些文檔對 protobuf 應該就有相當程度的了解了。

翻譯文檔見 [索引]文章索引,導航為翻譯 - 技術 - protobuf 官方文檔。

但是官方文檔更多的是作為查閱和權威參考,并不意味着看完官方文檔就能立馬了解其原理。

本文以及接下來的幾篇文章會對 protobuf 的編碼、序列化、反序列化、反射等原理做一些詳細介紹,同時也會盡量将這些原理表達的更為通俗易懂。

我們先來看看官方文檔給出的定義和描述:

protocol buffers 是一種語言無關、平台無關、可擴充的序列化結構資料的方法,它可用于(資料)通信協定、資料存儲等。 protocol buffers 是一種靈活,高效,自動化機制的結構資料序列化方法-可類比 xml,但是比 xml 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更為簡單。 你可以定義資料的結構,然後使用特殊生成的源代碼輕松的在各種資料流中使用各種語言進行編寫和讀取結構資料。你甚至可以更新資料結構,而不破壞由舊資料結構編譯的已部署程式。

簡單來講, protobuf 是結構資料序列化[1] 方法,可簡單類比于 xml[2],其具有以下特點:

語言無關、平台無關。即 protobuf 支援 java、c++、python 等多種語言,支援多個平台

高效。即比 xml 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更為簡單

擴充性、相容性好。你可以更新資料結構,而不影響和破壞原有的舊程式

序列化[1]:将結構資料或對象轉換成能夠被存儲和傳輸(例如網絡傳輸)的格式,同時應當要保證這個序列化結果在之後(可能在另一個計算環境中)能夠被重建回原來的結構資料或對象。 更為詳盡的介紹可參閱 維基百科。 類比于 xml[2]:這裡主要指在資料通信和資料存儲應用場景中序列化方面的類比,但個人認為 xml 作為一種擴充标記語言和 protobuf 還是有着本質差別的。

對 protobuf 的基本概念有了一定了解之後,我們來看看具體該如何使用 protobuf。

第一步,建立 .proto 檔案,定義資料結構,如下例1所示:

我們在上例中定義了一個名為 example1 的 消息,文法很簡單,message 關鍵字後跟上消息名稱:

之後我們在其中定義了 message 具有的字段,形式為:

在上例中,我們定義了:

類型 string,名為 stringval 的 optional 可選字段,字段編号為 1,此字段可出現 0 或 1 次

類型 bytes,名為 bytesval 的 optional 可選字段,字段編号為 2,此字段可出現 0 或 1 次

類型 embeddedmessage(自定義的内嵌 message 類型),名為 embeddedexample1 的 optional 可選字段,字段編号為 3,此字段可出現 0 或 1 次

類型 int32,名為 repeatedint32val 的 repeated 可重複字段,字段編号為 4,此字段可出現 任意多次(包括 0)

類型 string,名為 repeatedstringval 的 repeated 可重複字段,字段編号為 5,此字段可出現 任意多次(包括 0)

關于 proto2 定義 message 消息的更多文法細節,例如具有支援哪些類型,字段編号配置設定、import

導入定義,reserved 保留字段等知識請參閱 [翻譯] protobuf 官方文檔(二)- 文法指引(proto2)。

關于定義時的一些規範請參閱 [翻譯] protobuf 官方文檔(四)- 規範指引

第二步,protoc 編譯 .proto 檔案生成讀寫接口

我們在 .proto 檔案中定義了資料結構,這些資料結構是面向開發者和業務程式的,并不面向存儲和傳輸。

當需要把這些資料進行存儲或傳輸時,就需要将這些結構資料進行序列化、反序列化以及讀寫。那麼如何實作呢?不用擔心, protobuf 将會為我們提供相應的接口代碼。如何提供?答案就是通過 protoc 這個編譯器。

可通過如下指令生成相應的接口代碼:

最終生成的代碼将提供類似如下的接口:

深入 ProtoBuf - 簡介

例子-序列化和解析接口.png

深入 ProtoBuf - 簡介

例子-protoc 生成接口.png

第三步,調用接口實作序列化、反序列化以及讀寫

針對第一步中例1定義的 message,我們可以調用第二步中生成的接口,實作測試代碼如下:

關于 protoc 的使用以及接口調用的更多資訊可參閱 [翻譯] protobuf 官方文檔(九)- (c++開發)教程

關于例1的完整代碼請參閱 源碼:protobuf 例1。其中的 single_length_delimited_all.* 為例子相關代碼和檔案。

因為此系列文章重點在于深入 protobuf 的編碼、序列化、反射等原理,關于 protobuf 的文法、使用等隻做簡單介紹,更為詳見的使用教程可參閱我翻譯的系列官方文檔。

官方文檔以及網上很多文章提到 protobuf 可類比 xml 或 json。

那麼 protobuf 是否就等同于 xml 和 json 呢,它們是否具有完全相同的應用場景呢?

個人認為如果要将 protobuf、xml、json 三者放到一起去比較,應該區分兩個次元。一個是資料結構化,一個是資料序列化。這裡的資料結構化主要面向開發或業務層面,資料序列化面向通信或存儲層面,當然資料序列化也需要“結構”和“格式”,是以這兩者之間的差別主要在于面向領域和場景不同,一般要求和側重點也會有所不同。資料結構化側重人類可讀性甚至有時會強調語義表達能力,而資料序列化側重效率和壓縮。

從這兩個次元,我們可以做出下面的一些思考。

xml 作為一種擴充标記語言,json 作為源于 js 的資料格式,都具有資料結構化的能力。

例如 xml 可以衍生出 html (雖然 html 早于 xml,但從概念上講,html 隻是預定義标簽的 xml),html 的作用是标記和表達網際網路中資源的結構,以便浏覽器更好的展示網際網路資源,同時也要盡可能保證其人類可讀以便開發人員進行編輯,這就是面向業務或開發層面的資料結構化。

再如 xml 還可衍生出 rdf/rdfs,進一步表達語義網中資源的關系和語義,同樣它強調資料結構化的能力和人類可讀。

關于 rdf/rdfs 和語義網的概念可查詢相關資料了解,或參閱 2-answer 系列-本體構模組化塊(一) 和 3-answer 系列-本體構模組化塊(二) ,文中有一些簡單介紹。

json 也是同理,在很多場合更多的是展現了資料結構化的能力,例如作為互動接口的資料結構的表達。在 mongodb 中采用 json 作為查詢語句,也是在發揮其資料結構化的能力。

當然,json、xml 同樣也可以直接被用來資料序列化,實際上很多時候它們也是這麼被使用的,例如直接采用 json、xml 進行網絡通信傳輸,此時 json、xml 就成了一種序列化格式,它發揮了資料序列化的能力。但是經常這麼被使用,不代表這麼做就是合理。實際将 json、xml 直接作用資料序列化通常并不是最優選擇,因為它們在速度、效率、空間上并不是最優。換句話說它們更适合資料結構化而非資料序列化。

扯完 xml 和 json,我們來看看 protobuf,同樣的 protobuf 也具有資料結構化的能力,其實也就是上面介紹的 message 定義。我們能夠在 .proto 檔案中,通過 message、import、内嵌 message 等文法來實作資料結構化,但是很容易能夠看出,protobuf 在資料結構化方面和 xml、json 相差較大,人類可讀性較差,不适合上面提到的 xml、json 的一些應用場景。

但是如果從資料序列化的角度你會發現 protobuf 有着明顯的優勢,效率、速度、空間幾乎全面占優,看完後面的 protobuf 編碼的文章,你更會了解 protobuf 是如何極盡所能的壓榨每一寸空間和性能,而其中的編碼原理正是 protobuf 的關鍵所在,message 的表達能力并不是 protobuf 最關鍵的重點。是以可以看出 protobuf 重點側重于資料序列化 而非 資料結構化。

最終對這些個人思考做一些小小的總結:

xml、json、protobuf 都具有資料結構化和資料序列化的能力

xml、json 更注重資料結構化,關注人類可讀性和語義表達能力。protobuf 更注重資料序列化,關注效率、空間、速度,人類可讀性差,語義表達能力不足(為保證極緻的效率,會舍棄一部分元資訊)

protobuf 的應用場景更為明确,xml、json 的應用場景更為豐富。