1. Gpb概述
在某電信項目中接觸到GPB,用於和合作方的數據交換。 在大數據領域使用的非常多。
GPB的全稱爲Google Protocol Buffer,是谷歌公司用於在大數據存儲及交換方面的一個開源的協議和開發庫。優勢在於定義好數據類型和格式,兩方都可以非常高效的寫入和讀取,並且存儲額外開銷非常小。
通常我們在小型的數據交換,使用json和xml進行封裝,但遇到大量數據時,比如每秒收集和傳輸1M甚至更多時,json等有很多的冗餘數據,佔用很多硬盤和帶寬資源,這時GPB的優勢就顯現出來。
GPB是一種高效的結構化數據存儲格式,可以將結構化數據序列化,即將數據保存在文件中,這在數據格式與編程語言無關,可以用於通訊協議和數據存儲。當然GPB也有一定的劣勢,例如其是二進制存儲而不是文本存儲,其開發和調試都需要工具來支持,在小量數據時,還是建議使用json等來交換數據。
2, 典型案例
假定我們有這樣一個需求,我們爲sina公司開發一個日誌收集及存儲系統,將所有的訪問日誌歸檔存儲,假定每秒有10萬用戶訪問sina,那麼我們將有10萬條記錄需要收集,日誌將訪問,平均每條記錄150字節將會10萬*150=15M/s,即每秒收集15M的數據。假定我們收集的數據包括以下幾部分
源IP,源端口,使用的UA代理,目標URL, 訪問時間。
通常分爲客戶端和服務器,客戶端收集數據然後發給服務器,因此我們定義好數據結構。
可能有多種的方式實現這個功能,方案1,客戶端通常是讀取syslog,然後封裝爲GPB,發送給服務器,服務端收取GPB然後按照自己的格式加入到大數據系統。
方案2,客戶端寫gpb文件,通過ftp等傳遞給服務器,然後服務器解析,或者直接保存。
總的來說均需要寫入和讀取。因此我們僅介紹寫入和讀取功能。因此分三部分介紹,分別爲讀取,寫入,兩者之間的協議定義。
3. GPB對象定義(協議定義)
我們用於交換數據的的訪問日誌,包含以下及部分:
- srcIP IP地址,佔用四個字節。
- srcPort 源端口,佔用兩個字節。
- Agent; 字符串, 設定最大佔用64字節。
- 目標url: 字符串, 設定最大佔用128字節(如果超長即截斷)
- 訪問時間, 自1970年的秒值。佔用4個字節。
package bj; message WebLog{ { required int32 time = 1; required int32 srcIP = 2; required int32 srcPort = 3; required string url = 4; optional string Agent = 5; }
兩種類型,一種爲整形數字,另外一種爲字符串。還有兩種修飾符,一種爲required,表示必須帶有,如果沒有將判斷爲錯誤。另外一種爲optional,表示可選的,如果沒有不做處理。GPB在保存時僅保存了數字索引,因此其佔用空間非常小,但因此其保存的文件失去了一定的自解釋性。
保存爲文件weblog.proto,然後編譯生成C++代碼,命令爲:
protoc --cpp_out=./ weblog.proto
這將生成weblog.pb.cc weblog.pb.h兩個文件,這樣兩者之間的協議就定義好了,客戶端使用這個頭文件中定義的方法來寫入數據,服務器通過方法來讀取數據。
使用protoc命令來編譯gpb協議定義文件時,對於C++來說,編譯器會爲每一個消息創建一個對應的類,如果定義了packet,那將定義一個名字空間。 –cpp_out表示在指定的目錄裏面生成c++代碼,可以使用“protoc -h”來查看更多的用法。下一步我們來編寫讀取代碼。
4. gpb數據生成
寫入文件:
int write() { bj::WebLog log; int now = time(NULL); log.set_timestamp(now); log.set_srcip(1); log.set_srcport(2); log.set_url("http://www.sina.com.cn/news"); log.set_agent("firefox"); fstream output("a.log", ios::out | ios::app | ios::binary); if(!log.SerializeToOstream(&output)){ cout << "Failed to write msg."<<endl; return -1; } return 0; }
追加到最後。
5. gpb數據讀取
int read() { bj::WebLog log; fstream fin("a.log", ios::in | ios::binary); if(!log.ParseFromIstream(&fin)) { cout<<"Failed to parrse word! "<< endl; return -1; } cout<<log.url()<<endl; cout<<log.agent()<<endl; return 0; }
讀取第一個並輸出。
6. Python使用
python setup.py build python setup.py test python setup.py install