溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Protobuf使用規(guī)范分享

發(fā)布時間:2020-08-02 22:35:27 來源:網(wǎng)絡 閱讀:936 作者:鄒君安 欄目:開發(fā)技術

一、Protobuf 的優(yōu)點

  Protobuf 有如 XML,不過它更小、更快、也更簡單。它以高效的二進制方式存儲,比 XML 小 3 到 10 倍,快 20 到 100 倍。你可以定義自己的數(shù)據(jù)結(jié)構,然后使用代碼生成器生成的代碼來讀寫這個數(shù)據(jù)結(jié)構。你甚至可以在無需重新部署程序的情況下更新數(shù)據(jù)結(jié)構。只需使用 Protobuf 對數(shù)據(jù)結(jié)構進行一次描述,即可利用各種不同語言或從各種不同數(shù)據(jù)流中對你的結(jié)構化數(shù)據(jù)輕松讀寫。
   有兩項技術保證了采用 Protobuf 的程序能獲得相對于 XML 極大的性能提高。
第一點,我們可以考察 Protobuf 序列化后的信息內(nèi)容。您可以看到 Protocol Buffer 信息的表示非常緊湊,這意味著消息的體積減少,自然需要更少的資源。比如網(wǎng)絡上傳輸?shù)淖止?jié)數(shù)更少,需要的 IO 更少等,從而提高性能。

第二點,我們需要理解 Protobuf 封解包的大致過程,從而理解為什么會比 XML 快很多。詳細看以下鏈接 http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/

另外,它有一個非常棒的特性,即“向后”兼容性好,人們不必破壞已部署的、依靠“老”數(shù)據(jù)格式的程序就可以對數(shù)據(jù)結(jié)構進行升級。這樣您的程序就可以不必擔心因為消息結(jié)構的改變而造成的大規(guī)模的代碼重構或者遷移的問題。因為添加新的消息中的 field 并不會引起已經(jīng)發(fā)布的程序的任何改變。
Protobuf 語義更清晰,無需類似 XML 解析器的東西(因為 Protobuf 編譯器會將 .proto 文件編譯生成對應的數(shù)據(jù)訪問類以對 Protobuf 數(shù)據(jù)進行序列化、反序列化操作)。 使用 Protobuf 無需學習復雜的文檔對象模型,Protobuf 的編程模式比較友好,簡單易學,同時它擁有良好的文檔和示例,對于喜歡簡單事物的人們而言,Protobuf 比其他的技術更加有吸引力。

二、Protobuf消息定義

  消息由至少一個字段組合而成,類似于C語言中的結(jié)構。每個字段都有一定的格式。
  字段格式:限定修飾符① | 數(shù)據(jù)類型② | 字段名稱③ | = | 字段編碼值④ | 字段默認值⑤
  1)限定修飾符包含 required\optional\repeated
  Required: 表示是一個必須字段,必須相對于發(fā)送方,在發(fā)送消息之前必須設置該字段的值,對于接收方,必須能夠識別該字段的意思。發(fā)送之前沒有設置required字段或者無法識別required字段都會引發(fā)編解碼異常,導致消息被丟棄。至于為什么感興趣的自己可以到protobuf官網(wǎng)深入研究其編解碼原理。http://code.google.com/p/protobuf/
  Optional:表示是一個可選字段,可選對于發(fā)送方,在發(fā)送消息時,可以有選擇性的設置或者不設置該字段的值。對于接收方,如果能夠識別可選字段就進行相應的處理,如果無法識別,則忽略該字段,消息中的其它字段正常處理。---因為optional字段的特性,很多接口在升級版本中都把后來添加的字段都統(tǒng)一的設置為optional字段,這樣老的版本無需升級程序也可以正常的與新的軟件進行通信,只不過新的字段無法識別而已,因為并不是每個節(jié)點都需要新的功能,因此可以做到按需升級和平滑過渡。
  Repeated:表示該字段可以包含0,N個元素。其特性和optional一樣,但是每一次可以包含多個值。可以看作是在傳遞一個數(shù)組的值。
  
  2)數(shù)據(jù)類型 
  Protobuf定義了一套基本數(shù)據(jù)類型。幾乎都可以映射到C\C++\Java等語言的基礎數(shù)據(jù)類型. 
  



  另外,有一點特意強調(diào)一下:
  關于 fixed32 和int32的區(qū)別。fixed32的打包效率比int32的效率高,但是使用的空間一般比int32多。因此一個屬于時間效率高,一個屬于空間效率高。根據(jù)項目的實際情況,一般選擇fixed32,如果遇到對傳輸數(shù)據(jù)量要求比較苛刻的環(huán)境,可以選擇int32.
  3)字段編碼值
  有了該值,通信雙方才能互相識別對方的字段。當然相同的編碼值,其限定修飾符和數(shù)據(jù)類型必須相同.
編碼值的取值范圍為 1~2^32(4294967296)。其中 1~15的編碼時間和空間效率都是最高的,編碼值越大,其編碼的時間和空間效率就越低(相對于1-15),當然一般情況下相鄰的2個值編碼效率的是相同的,除非2個值恰好實在4字節(jié),12字節(jié),20字節(jié)等的臨界區(qū)。比如15和16.
有一點需要強調(diào),消息中的字段的編碼值無需連續(xù),只要是合法的,并且不能在同一個消息中有字段包含相同的編碼值。

三、protobuf編解碼原理

1) ProtoBuf編碼基礎——Varints, varints是一種將一個整數(shù)序列化為一個或者多個Bytes的方法,越小的整數(shù),使用的Bytes越少。
Varints的基本規(guī)則是:
(a) 每個Byte的最高位(msb)是標志位,如果該位為1,表示該Byte后面還有其它Byte,如果該位為0,表示該Byte是最后一個Byte。
(b)每個Byte的低7位是用來存數(shù)值的位
(c)Varints方法用Litte-Endian(小端)字節(jié)序 
2)ProtoBuf中消息的編碼規(guī)則:
(a)每條消息(message)都是有一系列的key-value對組成的, key和value分別采用不同的編碼方式。
(b)對某一條件消息(message)進行編碼的時候,是把該消息中所有的key-value對序列化成二進制字節(jié)流;而解碼的時候,解碼程序讀入二進制的字節(jié)流,解析出每一個key-value對,如果解碼過程中遇到識別不出來的類型,直接跳過。這樣的機制,保證了即使該消息添加了新的字段,也不會影響舊的編/解碼程序正常工作。 
(c)key由兩部分組成,一部分是字段編碼值(field_num),另一部分是字段類型(wire_type)。key = field_num << 3 | wire_type

 

類型含義用于
0Varintint32, int64, uint32, uint64, sint32, sint64, bool, enum
164-bitfixed64, sfixed64, double
2Length-delimitedstring, bytes, embedded messages, packed repeated fields
3Start groupgroups (deprecated)
4End groupgroups (deprecated)
532-bitfixed32, sfixed32, float

(d)varint類型(wire_type=0)的編碼,與第(1)部分中介紹的方法基本一致,但是int32, int64和sint32,sint64有些特別之處:int32和int64就是簡單的按varints方法來編碼,所以像-1、-2這樣負數(shù)也會占比較多的Bytes。于是sint32和sint64采用了一種改進的方法:先采用Zigzag方法將所有的整數(shù)(正數(shù)、0和負數(shù))一一映射到所有的無符號數(shù)上,然 后再采用varints編碼方法進行編碼。Zigzag映射函數(shù)為:
Zigzag(n) = (n << 1) ^ (n >> 31), n為sint32時
Zigzag(n) = (n << 1) ^ (n >> 63), n為sint64時
(f)64-bit(wire_type=1)和32-bit(wire_type=5)的編碼方式就比較簡單了,直接在key后面跟上64bits或32bits,采用Little-Endian(小端)字節(jié)序。
(g)length-delimited(wire_type=2)的編碼方式:key+length+content, key的編碼方式是統(tǒng)一的,length采用varints編碼方式,content就是由length指定的長度的Bytes。
(h)wire_type=3和4的現(xiàn)在已經(jīng)不推薦使用了,因此這里也不再做介紹。
如果希望更深入的理解它,可以從代碼上面去進一步研究,主要接口有protobuf_c_message_unpack、protobuf_c_message_pack、protobuf_c_message_free_unpacked等,路徑/wns/commonlibs/3party/protobuf-c-0.15/protobuf-c-0.15-low-memory-version/src/google/protobuf-c


實際使用過程當中,常常出現(xiàn)一些使用不當?shù)那闆r導致了程序異常,下面列舉常見的幾個情況:
1、新增字段限定修飾符設置為required(項目投入運營以后涉及到版本升級時的新增消息字段如果使用了required,需要全網(wǎng)統(tǒng)一升級)
2、多個版本同時在開發(fā)時,往同一個結(jié)構里邊加入相同字段編碼值的新optional字段(要知道字段編碼值正是處于這種兼容性的考慮)
第2種情況會導致版本兼容性問題,代碼示例如下:

發(fā)送端(AP),發(fā)送函數(shù):

void send_person_info_to_wac() 
{ 
    Wns__PersonInfo person = WNS__PERSON_INFO__INIT; 

    person.name = "sanzer"; 
    person.gender = 1; 
    person.has_option = 1; 
    person.option = 5; 
        
    wns_ipc_to_local_direct(WNS__MODID, 0, 
        WNS__MSGID, 
        (ProtobufCMessage *)&person); 
}

proto定義:

package wns; 
person_info{ 
	requried string name = 1; 
	required int32 gender = 2; 
	optioned int32 option = 3; 
}

接收端(WAC),接收函數(shù):

int32_t show_person_info_cb(const void *buf, int32_t len, 
                                   const ProtobufCMessage *msg, 
                                   const struct wns_cmd_ipc_hdr_t *proxy_hdr) 
{ 
    assert(msg); 
    Wns__PersonInfo *person = (Wns__PersonInfo *)msg;
    return 0;
}
// 注冊回調(diào)函數(shù): 
wns_ipc_reg_callback(WNS__MSGID, show_person_info_cb, 
    &wns__person_info__descriptor, AUTO_FREE);

proto定義: 

package wns;
person_info{
	require string name = 1;
	require int32 gender = 2;
	optioned double option = 3;
}

分別編譯兩種proto,并將動態(tài)庫拷貝到指定目錄。在接收端(wac)和發(fā)送端(ap)分別運行各自程序,然后觀察接收端解析的數(shù)據(jù)格式。

結(jié)果證明接收端與發(fā)送端option字段,都采用了相同的字段編碼值(3),但是不同的數(shù)據(jù)屬性(發(fā)送端:int32,接收端:double),這種情況下,接收端將會解析失敗。

四、protobuf使用規(guī)范

為了更好的使用它,現(xiàn)制定以下規(guī)范:
1、不要修改已經(jīng)存在的字段編碼值
2、新增字段必須為optional或repeated,否則無法保證新老程序在互相傳遞消息時的消息兼容性。
3、在原有的消息中,不能移除已經(jīng)存在的required字段,optional和repeated類型的字段可以被移除,但是他們之前使用的標簽號必須被保留,不能被新的字段重用。
4、新增字段標簽號可以不連續(xù)但不能重復。


向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI