Google 的开源技术protobuf 简介与例子
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
今天來介紹一下“Protocol?Buffers?”(以下簡稱protobuf)這個(gè)玩意兒。本來俺在構(gòu)思“生產(chǎn)者/消費(fèi)者模式 ”系列的下一個(gè)帖子:關(guān)于生產(chǎn)者和消費(fèi)者之間的數(shù)據(jù)傳輸格式。由于里面扯到了protobuf,想想干脆單獨(dú)開一個(gè)帖子算了。
★protobuf是啥玩意兒?
為了照顧從沒聽說過的同學(xué),照例先來掃盲一把。
首先,protobuf是一個(gè)開源?項(xiàng) 目(官方站點(diǎn)在“這里 ”),而且是后臺(tái)很硬的開源項(xiàng)目。網(wǎng)上現(xiàn)有的大部分(至少80%)開源項(xiàng)目,要么是某人單干、要么是幾個(gè)閑雜人等合伙搞。而protobuf則不然,它是 鼎鼎大名的Google公司開發(fā)出來,并且在Google內(nèi)部久經(jīng)考驗(yàn)的一個(gè)東東。由此可見,它的作者絕非一般閑雜人等可比。
那這個(gè)聽起來牛X的東東到底有啥用處捏?簡單地說,這個(gè)東東干的事兒其實(shí)和XML?差不多,也就是把某種數(shù)據(jù)結(jié)構(gòu)的信息,以某種格式保存起來。主要用于數(shù)據(jù)存儲(chǔ)、傳輸協(xié)議格式等場合。有同學(xué)可能心理犯嘀咕了:放著好好的XML不用,干嘛重新發(fā)明輪子啊?!先別急,后面俺自然會(huì)有說道。
話說到了去年(大約是08年7月),Google突然大發(fā)慈悲,把這個(gè)好東西貢獻(xiàn)給了開源社區(qū)。這下,像俺這種喜歡撿現(xiàn)成的家伙可就有福啦!貌似喜歡 撿現(xiàn)成的家伙還蠻多滴,再加上 Google的號召力,開源后不到一年,protobuf的人氣就已經(jīng)很旺了。所以俺為了與時(shí)俱進(jìn),就單獨(dú)開個(gè)帖子來忽悠一把。
★protobuf有啥特色?
掃盲完了之后,就該聊一下技術(shù)?方面的話題了。由于這玩意兒發(fā)布的時(shí)間較短(未滿周歲),所以俺接觸的時(shí)間也不長。今天在此是先學(xué)現(xiàn)賣,列位看官多多包涵 :-)
◇性能好/效率高
現(xiàn)在,俺就來說說Google公司為啥放著好端端的XML不用,非要另起爐灶,重新造輪子。一個(gè)根本的原因是XML性能不夠好。
先說時(shí)間開銷:XML格式化(序列化)的開銷倒還好;但是XML解析(反序列化)的開銷就不敢恭維啦。俺之前經(jīng)常碰到一些時(shí)間性能很敏感的場合,由于不堪忍受XML解析的速度,棄之如敝履。
再來看空間開銷:熟悉XML語法的同學(xué)應(yīng)該知道,XML格式為了有較好的可讀性,引入了一些冗余的文本信息。所以空間開銷也不是太好(不過這點(diǎn)缺點(diǎn),俺不常碰到)。
由于Google公司賴以吹噓的就是它的海量數(shù)據(jù)和海量處理能力。對于幾十萬、上百萬機(jī)器的集群,動(dòng)不動(dòng)就是PB級的數(shù)據(jù)量,哪怕性能稍微提高 0.1% 也是相當(dāng)可觀滴。所以Google自然無法容忍XML在性能上的明顯缺點(diǎn)。再加上Google從來就不缺造輪子的牛人,所以protobuf也就應(yīng)運(yùn)而生 了。
Google對于性能的偏執(zhí),那可是出了名的。所以,俺對于Google搞出來protobuf是非常滴放心,性能上不敢說是最好,但肯定不會(huì)太差。
◇代碼?生成機(jī)制
除了性能好,代碼生成機(jī)制是主要吸引俺的地方。為了說明這個(gè)代碼生成機(jī)制,俺舉個(gè)例子。
比如有個(gè)電子商務(wù)的系統(tǒng)(假設(shè)用C++實(shí)現(xiàn)),其中的模塊A需要發(fā)送大量的訂單信息給模塊B,通訊的方式使用socket。
假設(shè)訂單包括如下屬性:
--------------------------------
時(shí)間:time(用整數(shù)表示)
客戶id:userid(用整數(shù)表示)
交易金額:price(用浮點(diǎn)數(shù)表示)
交易的描述:desc(用字符串表示)
--------------------------------
如果使用protobuf實(shí)現(xiàn),首先要寫一個(gè)proto文件(不妨叫Order.proto),在該文件中添加一個(gè)名為"Order"的message結(jié)構(gòu),用來描述通訊協(xié)議中的結(jié)構(gòu)化數(shù)據(jù)。該文件的內(nèi)容大致如下:
?
--------------------------------
message Order
{
? required int32 time = 1;
? required int32 userid = 2;
? required float price = 3;
? optional string desc = 4;
}
--------------------------------
?
然后,使用protobuf內(nèi)置的編譯器編譯 該proto。由于本例子的模塊是C++,你可以通過protobuf編譯器的命令行參數(shù)(看“這里 ”),讓它生成C++語言的“訂單包裝類”。(一般來說,一個(gè)message結(jié)構(gòu)會(huì)生成一個(gè)包裝類)
然后你使用類似下面的代碼來序列化/解析該訂單包裝類:
--------------------------------
// 發(fā)送方
Order order;
order.set_time(XXXX);
order.set_userid(123);
order.set_price(100.0f);
order.set_desc("a test order");
string sOrder;
order.SerailzeToString(&sOrder);
// 然后調(diào)用某種socket的通訊庫把序列化之后的字符串發(fā)送出去
// ......
--------------------------------
// 接收方
string sOrder;
// 先通過網(wǎng)絡(luò)通訊庫接收到數(shù)據(jù),存放到某字符串sOrder
// ......
Order order;
if(order.ParseFromString(sOrder))? // 解析該字符串
{
? cout << "userid:" << order.userid() << endl
????????? << "desc:" << order.desc() << endl;
}
else
{
? cerr << "parse error!" << endl;
}
--------------------------------
?
有了這種代碼生成機(jī)制,開發(fā)人員再也不用吭哧吭哧地編寫那些協(xié)議解析的代碼了(干這種活是典型的吃力不討好)。
萬一將來需求發(fā)生變更,要求給訂單再增加一個(gè)“狀態(tài)”的屬性,那只需要在Order.proto文件中增加一行代碼。對于發(fā)送方(模塊A),只要增加一行設(shè)置狀態(tài)的代碼;對于接收方(模塊B)只要增加一行讀取狀態(tài)的代碼。哇塞,簡直太輕松了!
另外,如果通訊雙方使用不同的編程語言來實(shí)現(xiàn),使用這種機(jī)制可以有效確保兩邊的模塊對于協(xié)議的處理是一致的。
順便跑題一下。
從某種意義上講,可以把proto文件看成是描述通訊協(xié)議的規(guī)格說明書(或者叫接口規(guī)范)。這種伎倆其實(shí)老早就有了,搞過微軟的COM編程或者接觸過CORBA的同學(xué),應(yīng)該都能從中看到IDL(詳細(xì)解釋看“這里 ”)的影子。它們的思想是相通滴。
◇支持“向后兼容”和“向前兼容”
還是拿剛才的例子來說事兒。為了敘述方便,俺把增加了“狀態(tài)”屬性的訂單協(xié)議成為“新版本”;之前的叫“老版本”。
所謂的“向后兼容”(backward compatible),就是說,當(dāng)模塊B升級了之后,它能夠正確識(shí)別模塊A發(fā)出的老版本的協(xié)議。由于老版本沒有“狀態(tài)”這個(gè)屬性,在擴(kuò)充協(xié)議時(shí),可以考 慮把“狀態(tài)”屬性設(shè)置成非必填 的,或者給“狀態(tài)”屬性設(shè)置一個(gè)缺省值(如何設(shè)置缺省值,參見“這里 ”)。
所謂的“向前兼容”(forward compatible),就是說,當(dāng)模塊A升級了之后,模塊B能夠正常識(shí)別模塊A發(fā)出的新版本的協(xié)議。這時(shí)候,新增加的“狀態(tài)”屬性會(huì)被忽略。
“向后兼容”和“向前兼容”有啥用捏?俺舉個(gè)例子:當(dāng)你維護(hù)一個(gè)很龐大的分布式系統(tǒng)時(shí),由于你無法同時(shí) 升級所有 模塊,為了保證在升級過程中,整個(gè)系統(tǒng)能夠盡可能不受影響,就需要盡量保證通訊協(xié)議的“向后兼容”或“向前兼容”。
◇支持多種編程語言
俺開博以來點(diǎn)評?的幾個(gè)開源項(xiàng)目(比如“Sqlite ”、“cURL ”),都是支持很多種 編程語言滴,這次的protobuf也不例外。在Google官方發(fā)布的源代碼中包含了C++、Java?、Python三種語言(正好也是俺最常用的三種,真爽)。如果你平時(shí)用的就是這三種語言之一,那就好辦了。
假如你想把protobuf用于其它語言,咋辦捏?由于Google一呼百應(yīng)的號召力,開源社區(qū)對protobuf響應(yīng)踴躍,近期冒出很多其它編程語言的版本(比如ActionScript、C#、Lisp、Erlang、Perl、PHP?、Ruby等),有些語言還同時(shí)搞出了多個(gè)開源的項(xiàng)目。具體細(xì)節(jié)可以參見“這里 ”。
不過俺有義務(wù)提醒一下在座的各位同學(xué)。如果你考慮把protobuf用于上述這些語言,一定認(rèn)真評估對應(yīng)的開源庫。因?yàn)檫@些開源庫不是Google官方提供的、而且出來的時(shí)間還不長。所以,它們的質(zhì)量、性能等方面可能還有欠缺。
★protobuf有啥缺陷?
前幾天剛剛在“光環(huán)效應(yīng) ”的帖子里強(qiáng)調(diào)了“要同時(shí)評估優(yōu)點(diǎn)和缺點(diǎn)”。所以俺最后再來批判一下這玩意兒的缺點(diǎn)。
◇應(yīng)用?不夠廣
由于protobuf剛公布沒多久,相比XML而言,protobuf還屬于初出茅廬。因此,在知名度、應(yīng)用廣度等方面都遠(yuǎn)不如XML。由于這個(gè)原因,假如你設(shè)計(jì)的系統(tǒng)需要提供若干對外的接口給第三方系統(tǒng)調(diào)用,俺奉勸你暫時(shí)不要考慮protobuf格式。
◇二進(jìn)制格式導(dǎo)致可讀性差
為了提高性能,protobuf采用了二進(jìn)制格式進(jìn)行編碼。這直接導(dǎo)致了可讀性差的問題(嚴(yán)格地說,是沒有可讀性)。雖然protobuf提供了TextFormat這個(gè)工具類(文檔在“這里 ”),但終究無法徹底解決此問題。
可讀性差的危害,俺再來舉個(gè)例子。比如通訊雙方如果出現(xiàn)問題,極易導(dǎo)致扯皮(都不承認(rèn)自己有問題,都說是對方的錯(cuò))。俺對付扯皮的一個(gè)簡單方法?就是直接抓包并dump成log,能比較容易地看出錯(cuò)誤在哪一方。但是protobuf的二進(jìn)制格式,導(dǎo)致你抓包并直接dump出來的log難以看懂。
◇缺乏自描述
一般來說,XML是自描述的,而protobuf格式則不是。給你一段二進(jìn)制格式的協(xié)議內(nèi)容,如果不配合相應(yīng)的proto文件,那簡直就像天書一般。
由于“缺乏自描述”,再加上“二進(jìn)制格式導(dǎo)致可讀性差”。所以在配置文件方面,protobuf是肯定無法取代XML的地位滴。
★為什么俺會(huì)用上protobuf?
俺自從前段時(shí)間接觸了protobuf之后,就著手把俺負(fù)責(zé)的產(chǎn)品中的部分 數(shù)據(jù)傳輸協(xié)議替換成protobuf。可能有同學(xué)會(huì)問,和protobuf類似的東東也有不少,為啥獨(dú)獨(dú)相中protobuf捏?由于今天寫的篇幅已經(jīng)蠻 長了,俺賣個(gè)關(guān)子,把這個(gè)話題留到“生產(chǎn)者/消費(fèi)者模式[5]:如何選擇傳輸協(xié)議及格式?”。俺會(huì)在后續(xù)的這個(gè)帖子里對比各種五花八門的協(xié)議格式,并談?wù)?俺的淺見。v
轉(zhuǎn)載于:https://my.oschina.net/sniperLi/blog/901604
總結(jié)
以上是生活随笔為你收集整理的Google 的开源技术protobuf 简介与例子的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python 随笔
- 下一篇: Strange Optimization