深入理解Protobuf原理與工程實(shí)踐(概述)

什么是Protobuf

Protobuf(Protocol Buffers)是一種跨平臺、語言無關(guān)括改、可擴(kuò)展的序列化結(jié)構(gòu)數(shù)據(jù)的方法,可用于網(wǎng)絡(luò)數(shù)據(jù)交換及存儲。
在序列化結(jié)構(gòu)化數(shù)據(jù)的機(jī)制中荸哟,Protobuf是靈活、高效瞬捕、自動化的鞍历,相對常見的XML、JSON肪虎,描述同樣的信息劣砍,Protobuf序列化后數(shù)據(jù)量更小、序列化/反序列化速度更快扇救、更簡單刑枝。
一旦定義了要處理的數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)之后,就可以利用Protobuf的代碼生成工具生成相關(guān)的代碼迅腔。只需使用 Protobuf 對數(shù)據(jù)結(jié)構(gòu)進(jìn)行一次描述装畅,即可利用各種不同語言(proto3支持C++, Java, Python, Go, Ruby, Objective-C, C#)或從各種不同流中對你的結(jié)構(gòu)化數(shù)據(jù)輕松讀寫。

為什么Protobuf

大家可能會覺得 Google 發(fā)明Protobuf是為了解決序列化速度的沧烈,其實(shí)真實(shí)的原因并不是這樣的掠兄。
Protobuf最先開始是 Google用來解決索引服務(wù)器 request/response 協(xié)議的。沒有Protobuf之前,google 已經(jīng)存在了一種 request/response 格式徽千,用于手動處理 request/response 的編解碼苫费。它也能支持多版本協(xié)議,不過代碼不夠優(yōu)雅:

if (protocolVersion=1) {
    doSomething();
} else if (protocolVersion=2) {
    doOtherThing();
} ...

如果是非常明確的格式化協(xié)議双抽,會使新協(xié)議變得非常復(fù)雜百框。因?yàn)殚_發(fā)人員必須確保請求發(fā)起者與處理請求的實(shí)際服務(wù)器之間的所有服務(wù)器都能理解新協(xié)議,然后才能切換開關(guān)以開始使用新協(xié)議牍汹。
這也就是每個服務(wù)器開發(fā)人員都遇到過的低版本兼容铐维、新舊協(xié)議兼容相關(guān)的問題。
為了解決這些問題慎菲,于是Protobuf就誕生了嫁蛇。Protobuf最初被寄予以下 2 個特點(diǎn):

  • 可以很容易地引入新的字段,并且不需要檢查數(shù)據(jù)的中間服務(wù)器可以簡單地解析并傳遞數(shù)據(jù)露该,而無需了解所有字段睬棚。
  • 數(shù)據(jù)格式更加具有自我描述性,可以用各種語言來處理(C++, Java 等各種語言)

這個版本的 Protobuf仍需要自己手寫解析的代碼解幼。
不過隨著系統(tǒng)慢慢發(fā)展抑党,演進(jìn),Protobuf具有了更多的特性:

  • 自動生成的序列化和反序列化代碼避免了手動解析的需要撵摆。(官方提供自動生成代碼工具底靠,各個語言平臺的基本都有)

  • 除了用于數(shù)據(jù)交換之外,Protobuf被用作持久化數(shù)據(jù)的便捷自描述格式

Protobuf現(xiàn)在是Google用于數(shù)據(jù)交換和存儲的通用語言特铝。谷歌代碼樹中定義了 48162 種不同的消息類型暑中,包括 12183 個 .proto 文件。它們既用于 RPC 系統(tǒng)鲫剿,也用于在各種存儲系統(tǒng)中持久存儲數(shù)據(jù)鳄逾。

Protobuf誕生之初是為了解決服務(wù)器端新舊協(xié)議(高低版本)兼容性問題,名字也很體貼灵莲,“協(xié)議緩沖區(qū)”严衬。只不過后期慢慢發(fā)展成用于傳輸數(shù)據(jù)。

Protocol Buffers 命名由來:

Why the name "Protocol Buffers"?
The name originates from the early days of the format, before we had the protocol buffer compiler to generate classes for us. At the time, there was a class called ProtocolBuffer which actually acted as a buffer for an individual method. Users would add tag/value pairs to this buffer individually by calling methods like AddValue(tag, value). The raw bytes were stored in a buffer which could then be written out once the message had been constructed.
Since that time, the "buffers" part of the name has lost its meaning, but it is still the name we use. Today, people usually use the term "protocol message" to refer to a message in an abstract sense, "protocol buffer" to refer to a serialized copy of a message, and "protocol message object" to refer to an in-memory object representing the parsed message.

如何使用Protobuf

Protobuf協(xié)議的工作流程

image

可以看到笆呆,對于序列化協(xié)議來說,使用方只需要關(guān)注業(yè)務(wù)對象本身粱挡,即idl定義赠幕,序列化和反序列化的代碼只需要通過工具生成即可。

Protobuf消息定義

Protobuf的消息是在idl文件(.proto)中描述的询筏。下面是本次樣例中使用到的消息描述符customer.proto:

syntax = "proto3";

package domain;

option java_package = "com.protobuf.generated.domain";
option java_outer_classname = "CustomerProtos";

message Customers {
    repeated Customer customer = 1;
}

message Customer {
    int32 id = 1;
    string firstName = 2;
    string lastName = 3;

    enum EmailType {
        PRIVATE = 0;
        PROFESSIONAL = 1;
    }

    message EmailAddress {
        string email = 1;
        EmailType type = 2;
    }

    repeated EmailAddress email = 5;
}

上面的消息比較簡單榕堰,Customers包含多個Customer,Customer包含一個id字段,一個firstName字段逆屡,一個lastName字段以及一個email的集合圾旨。
除了這些定義外,文件頂部還有三行可幫助代碼生成器:

  1. 首先魏蔗,syntax = "proto3"用于idl語法版本砍的,目前有兩個版本proto2和proto3,兩個版本語法不兼容莺治,如果不指定廓鞠,默認(rèn)語法是proto2。由于proto3比proto2支持的語言更多谣旁,語法更簡潔床佳,本文使用的是proto3。
  2. 其次有一個package domain;定義榄审。此配置用于嵌套生成的類/對象砌们。
  3. 有一個option java_package定義。生成器還使用此配置來嵌套生成的源搁进。此處的區(qū)別在于這僅適用于Java浪感。在使用Java創(chuàng)建代碼和使用JavaScript創(chuàng)建代碼時,使用了兩種配置來使生成器的行為有所不同拷获。也就是說篮撑,Java類是在包c(diǎn)om.protobuf.generated.domain下創(chuàng)建的,而JavaScript對象是在包domain下創(chuàng)建的匆瓜。

Protobuf提供了更多選項(xiàng)和數(shù)據(jù)類型赢笨,本文不做詳細(xì)介紹,感興趣可以參考這里

代碼生成

首先安裝Protobuf編譯器protoc驮吱,這里有詳細(xì)的安裝教程茧妒,安裝完成后,可以使用以下命令生成Java源代碼:

protoc --java_out=./src/main/java ./src/main/idl/customer.proto

從項(xiàng)目的根路徑執(zhí)行該命令左冬,并添加了兩個參數(shù):java_out桐筏,定義./src/main/java/為Java代碼的輸出目錄;而./src/main/idl/customer.proto是.proto文件所在目錄拇砰。
生成的代碼非常復(fù)雜梅忌,但是幸運(yùn)的是它的用法卻非常簡單。

        CustomerProtos.Customer.EmailAddress email = CustomerProtos.Customer.EmailAddress.newBuilder()
                .setType(CustomerProtos.Customer.EmailType.PROFESSIONAL)
                .setEmail("crichardson@email.com").build();

        CustomerProtos.Customer customer = CustomerProtos.Customer.newBuilder()
                .setId(1)
                .setFirstName("Lee")
                .setLastName("Richardson")
                .addEmail(email)
                .build();
        // 序列化
        byte[] binaryInfo = customer.toByteArray();
        System.out.println(bytes_String16(binaryInfo));
        System.out.println(customer.toByteArray().length);
        // 反序列化
        CustomerProtos.Customer anotherCustomer = CustomerProtos.Customer.parseFrom(binaryInfo);
        System.out.println(anotherCustomer.toString());

性能數(shù)據(jù)

我們簡單地以Customers為模型除破,分別構(gòu)造牧氮、選取小對象、普通對象瑰枫、大對象進(jìn)行性能對比
序列化耗時以及序列化后數(shù)據(jù)大小對比

image

反序列化耗時
image

更多性能數(shù)據(jù)可以參考官方Benchmark

總結(jié)

上面介紹了Protobuf是什么踱葛、產(chǎn)生的背景、基本用法,我們再總結(jié)下尸诽。
優(yōu)點(diǎn)

  • 效率高
    從序列化后的數(shù)據(jù)體積角度甥材,與XML、JSON這類文本協(xié)議相比性含,Protobuf通過T-(L)-V(TAG-LENGTH-VALUE)方式編碼洲赵,不需要", {, }, :等分隔符來結(jié)構(gòu)化信息,同時在編碼層面使用varint壓縮胶滋,所以描述同樣的信息板鬓,Protobuf序列化后的體積要小很多,在網(wǎng)絡(luò)中傳輸消耗的網(wǎng)絡(luò)流量更少究恤,進(jìn)而對于網(wǎng)絡(luò)資源緊張俭令、性能要求非常高的場景,Protobuf協(xié)議是不錯的選擇部宿。
// 我們簡單做個對比
// 要描述如下JSON數(shù)據(jù)
{"id":1,"firstName":"Chris","lastName":"Richardson","email":[{"type":"PROFESSIONAL","email":"crichardson@email.com"}]}
# 使用JSON序列化后的數(shù)據(jù)大小為118byte
7b226964223a312c2266697273744e616d65223a224368726973222c226c6173744e616d65223a2252696368617264736f6e222c22656d61696c223a5b7b2274797065223a2250524f46455353494f4e414c222c22656d61696c223a226372696368617264736f6e40656d61696c2e636f6d227d5d7d
# 而使用Protobuf序列化后的數(shù)據(jù)大小為48byte
0801120543687269731a0a52696368617264736f6e2a190a156372696368617264736f6e40656d61696c2e636f6d1001

從序列化/反序列化速度角度抄腔,與XML、JSON相比理张,Protobuf序列化/反序列化的速度更快赫蛇,比XML要快20-100倍。

2.支持跨平臺雾叭、多語言
Protobuf是平臺無關(guān)的悟耘,無論是android與PC,還是C#與Java都可以利用Protobuf進(jìn)行無障礙通訊织狐。
proto3支持C++, Java, Python, Go, Ruby, Objective-C, C#暂幼。

3.擴(kuò)展性、兼容性好移迫。
具有向后兼容的特性旺嬉,更新數(shù)據(jù)結(jié)構(gòu)以后,老版本依舊可以兼容厨埋,這也是Protobuf誕生之初被寄予解決的問題邪媳。因?yàn)榫幾g器對不識別的新增字段會跳過不處理。

4.使用簡單
Protobuf提供了一套編譯工具荡陷,可以自動生成序列化雨效、反序列化的樣板代碼,這樣開發(fā)者只要關(guān)注業(yè)務(wù)數(shù)據(jù)idl废赞,簡化了編碼解碼工作以及多語言交互的復(fù)雜度徽龟。

缺點(diǎn)
1.可讀性差,缺乏自描述
XML蛹头,JSON是自描述的,而Protobuf則不是。
Protobuf是二進(jìn)制協(xié)議渣蜗,編碼后的數(shù)據(jù)可讀性差屠尊,如果沒有idl文件,就無法理解二進(jìn)制數(shù)據(jù)流耕拷,對調(diào)試不友好讼昆。
不過Charles已經(jīng)支持Protobuf協(xié)議,導(dǎo)入數(shù)據(jù)的描述文件即可骚烧,詳情可參考Charles Protocol Buffers浸赫。
此外,由于沒有idl文件無法解析二進(jìn)制數(shù)據(jù)流赃绊,Protobuf在一定程度上可以保護(hù)數(shù)據(jù)既峡,提升核心數(shù)據(jù)被破解的門檻,降低核心數(shù)據(jù)被盜爬的風(fēng)險碧查。

參考

維基百科
序列化與反序列化
官方Benchmark
Charles Protocol Buffers
choose-protocol-buffers

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末运敢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子忠售,更是在濱河造成了極大的恐慌传惠,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稻扬,死亡現(xiàn)場離奇詭異卦方,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)泰佳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進(jìn)店門盼砍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人乐纸,你說我怎么就攤上這事衬廷。” “怎么了汽绢?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵吗跋,是天一觀的道長。 經(jīng)常有香客問我宁昭,道長跌宛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任积仗,我火速辦了婚禮疆拘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寂曹。我一直安慰自己哎迄,他們只是感情好回右,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著漱挚,像睡著了一般翔烁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上旨涝,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天蹬屹,我揣著相機(jī)與錄音,去河邊找鬼白华。 笑死慨默,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的弧腥。 我是一名探鬼主播厦取,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鸟赫!你這毒婦竟也來了蒜胖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤抛蚤,失蹤者是張志新(化名)和其女友劉穎台谢,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體岁经,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡朋沮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了缀壤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片樊拓。...
    茶點(diǎn)故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖塘慕,靈堂內(nèi)的尸體忽然破棺而出筋夏,到底是詐尸還是另有隱情,我是刑警寧澤图呢,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布条篷,位于F島的核電站,受9級特大地震影響蛤织,放射性物質(zhì)發(fā)生泄漏赴叹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一指蚜、第九天 我趴在偏房一處隱蔽的房頂上張望乞巧。 院中可真熱鬧,春花似錦摊鸡、人聲如沸绽媒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽是辕。三九已至播瞳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間免糕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工忧侧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留石窑,地道東北人。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓蚓炬,卻偏偏與公主長得像松逊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子肯夏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評論 2 356