造輪子系列之Protobuf

做爲一個程序猿,對造輪子這事情能夠說是情有獨鍾,幾乎程序猿心裏都存在一個夢想是去將開源的技術都實現一遍,全部從本篇開始,我會開一個造輪子系列。

前言

首先,看看這個,想必你們對下面這種簡歷看得比較多了吧?
  1. 精通JAVA,Python,熟練掌握C++
  2. 精通Redis,Memcached,Mysql
  3. 精通Nginx配置,模塊開發
  4. 精通Kafka,ActiveMQ 等消息隊列
  5. 精一般用數據結構和算法
  6. 精通網絡編程,多線程編程技術,高性能服務器技術
  7. 精通tcp/ip協議棧,熟悉內核網絡子系統代碼
  8. 精通nginx代碼及模塊開發
上面每一條都涉及好多輪子,每個都是精通,若是真能作到。那這我的能夠說是碼農中的戰鬥機。
那咱們如今目標就是去作這個戰鬥機。而這個方法,就是本身去造輪子,造的目的不是爲了在項目中使用本身造的輪子,而是爲了去了解輪子的構造,而後本身動手去體會造輪子的過程。



後端的輪子們

提及後端的輪子們,你們均可以說出一大串來,咱們大體來數一數啊。
  • 抗在最前面的:LVS,F5,HAProxy這類負載均衡
  • 接下來有Nginx,Apache,Lighttpd這類Http服務
  • http服務後則是各類容器,部署着咱們的業務邏輯
  • 存儲這邊有Redis,Memcached這一類KV存儲器和緩存系統
  • 若是是多機部署,確定還有Kafka,ActiveMQ這種負責解耦的消息隊列
  • 爲了實現集羣通訊,確定少不了Thrift這種RPC框架和Protobuf這種序列化技術
  • 再高端點,到了分佈式領域了,就是更多的輪子了。。zookeeper、raft等等
  • 還有大數據系列hadoop。spark。。。。。

本文先開始咱們的第一個輪子,服務器通訊須要用的數據序列化反序列技術:protobuf。

基礎輪子:protobuf

講基礎前,先附上一張極客時間中的一個技術須要從哪些角度來說的圖片,本文也會盡量從這些個方面來說。

  • 應用角度
    • 問題:」幹什麼用「
    • 技術規範:」怎麼用「
    • 最佳實踐:」怎麼能用好「
    • 市場應用趨勢:「誰用,用在哪」
  • 設計角度
    • 目標:「作到什麼」
    • 實現原理:「怎麼作到」
    • 優劣侷限:「作得怎麼樣」
    • 演進趨勢:「將來如何」


正文

Protocol buffers

應用角度

幹什麼用的?
序列化數據用的?何時須要序列化?當數據須要存儲或者網絡傳輸的時候。爲何呢?
在存儲或者傳輸的時候,咱們能看到都是一些二進制數據,即010101……的bit。
假設咱們看的一個對象是:

Struct myData  { Int a; Int b;}data = myData { a:1, b:2,}

那咱們在網絡上收到是一個字節流,咱們爲了可以從字節流中恢復出數據 data,咱們要作的工做是:css


  1. 正確識別出data在字節流中開始和結束的位置
  2. 識別出a的值,識別出b的值
一個可能的字節流協議就是:

剛開始是8bit標明後續數據是哪一個結構,而後是兩個4字節表示a和b。
注意!!!!!上面作出上面這個假設,有幾點是咱們默認的:
  1. 咱們認爲字節流開始先是8bit標明是哪一個數據結構,此處是myData(ps:不一樣結構之間編號不一樣)
  2. 最多可以支持2^8種結構
  3. 通信雙方都須要拿到myData的定義文件

如下是一個上面實現的示例代碼見GitHub。


能夠看到在go中很容易就實現了咱們的一個數據結構的序列化反序列化。

設計角度
作到什麼?
上面咱們只是實現了一個最簡單的實現了一個序列化方法,下面咱們來看若是要實現一個生產環境中的序列化協議,須要作到哪幾點。

  1. 通用性:語言、平臺無關
  2. 高性能:序列化和反序列化都要快
  3. 高壓縮:序列化後數據儘量小,小就意味着網絡傳輸數據少
  4. 兼容性:數據結構改變了,也可以支持新老版本

下面咱們帶着這些目標來從應用角度來看下」怎麼用「
這個就是官方文檔了 developers.google.com/protocol-bu…,裏面有詳細的說明,另外我本身給了一份使用示例: github.com/zhuanxuhit/…

講完使用下面就是設計角度:如何作到的?

首先咱們看前文咱們本身實現的簡易序列化、反序列方法,咱們對每一個結構進行編碼,而後在頭部寫上該結構是啥,而後後面就是結構中每一個字段的具體值,接着咱們寫了序列化器的目標是:
簡易、高效、兼容,下面咱們從這幾個方面來看protobuf有什麼改進的地方。

先來個小插曲,protobuf全稱是Protocol buffers,其中buffers點名了使用上很是重要的一個點,即咱們在反序列化的一段二進制數據的時候,咱們要將其先讀入到buffer中,而後再識別出單個數據結構的開頭和結尾,最後才能正確的反序列化出來。

前面咱們設計的時候,還在頭部對數據結構進行了編碼,那爲了可以作到更高效,數據更小,咱們是否能夠把這個頭也去掉呢?
固然是能夠的,因而咱們的結構就變爲了只有對應的字段值了,這麼作的一個前提是:!!咱們必須清楚知道咱們識別出來二進制數據,其對應的具體是哪一個數據結構。!!

如今咱們去掉告終構描述,那怎麼可以作到更小呢?譬如一樣是int64,1 和 1<<32不必都用8字節來表示,譬如咱們能夠先對數據類型作一個編碼,而後緊跟着後續使用的bit,再跟着真正的數據。
每一個部分分別用幾個bit來表示呢?

  • 數據類型:根據支持的類型進行編碼,若是總共能支持16種類型,那就是4bit
  • 後續有效字節:這個比較難辦,因爲咱們不肯定數據大小,咱們就沒法固定bit來表示。
那一個解決方法就是:咱們去掉有效字節的字段,咱們把這個是否有更多數據信息編碼進數據自己中,示意圖以下:

解決了編碼int類型的字段後,若是遇到string類型呢?這種類型首先也是數據類型描述,接着應該要是編碼後續有效字節,這是一個int,這能夠採用上面的方法來編碼,再跟着就是有效數據了。
以上就是protobuf在編碼數據時採用編碼方式的主要思想,具體能夠看 developers.google.com/protocol-bu…

目前protobuf支持的數據類型

上面有個主意的對於有符號數,咱們要單獨處理下,由於有符號數的最高位是經過0,1來表示正負的,可是上面編碼中最高位卻用來表示是否有後續數據了,因此咱們要經過ZigZag 編碼將有符號轉換爲無符號。 

原理很簡單,就是經過下面的編碼方式: nginx



解決了編碼後,咱們來看最後一個問題:兼容性。

若是咱們改變了數據結構:新增或者刪除了字段怎麼辦???

這個也好解決,那咱們就給全部字段加上編號,經過字段來表示這個數據是結構體中哪一個字段,protobuf在設計上編碼方式以下:


從上圖中tag的編碼,咱們能夠發現,若是field_num > 16的話,tag編碼出來會使用超過1字節,全部對於咱們常用的字段,建議將其編碼到0-15,減小tag位數。


實現原理小結
下面咱們對上面介紹的實現原理作個小結

高效:變長編碼,非自描述
兼容:對filed進行編碼 


下面咱們再從應用角度講下 protobuf 的最佳實踐和市場應用趨勢。
protobuf剛開始設計出來主要是爲了解決接口兼容性問題,目前是主要用在內部服務之間RPC調用和傳遞數據,目前時候用protobuf做爲序列化的rpc框架有gRPC,這也是後續咱們會介紹的。

最後咱們從設計角度來看下protobuf的優劣侷限和演進趨勢。

優勢
protobuf最大的優勢就是先後兼容性,已經部署的使用老數據格式的服務,即便接口升級了也能夠繼續使用,而後就是性能,固然是快了,具體能夠看 序列化 / 反序列化性能

缺點
相比較json來講,可讀性差,特別是在調試階段,相比較json咱們沒法清晰的知道輸入和輸出。


最後
總結下本文
  1. Protobuf設計之初主要是爲了解決兼容性問題,實現上是對每一個字段進行編號,當遇到不存在的字段時,則忽略掉。
  2. Protobuf爲了可以作到高性能,在編碼時採用了Tag - Value (Tag - Length - Value)的方式,使序列化後的數據更緊湊
  3. Protobuf爲了可以作到高性能,丟棄了自描述信息,即咱們只拿到數據,而沒有拿到proto文件,咱們是沒法反序列數據的
  4. Protobuf提供了一套編譯工具,可以生成不一樣語言的數據序列化、反序列化方法,極大的提升了易用性

預告
下一篇咱們會介紹grpc,來看下rpc框架中是怎麼使用protobuf的。 
相關文章
相關標籤/搜索