IM通訊協議專題學習(二):快速理解Protobuf的背景、原理、使用、優缺點

JackJiang發表於2022-11-22
本文由vivo技術團隊Li Guanyun分享,為了提升閱讀體驗,進行了較多修訂和重新排版。

1、引言

Protobuf 作為一種跨平臺、語言無關、可擴充套件的序列化結構資料通訊協議,已廣泛應用於網路資料交換的場景中(比如IM通訊、分散式RPC呼叫等)。隨著網際網路的發展,分散式系統的異構性會愈發突出,跨語言的需求會愈加明顯,同時 gRPC 也大有取代Restful之勢,而 Protobuf 作為gRPC 跨語言、高效能的法寶,我們技術人有必要深入理解 Protobuf 原理,為以後的技術更新和選型打下基礎。藉此機會,我將個人的Protobuf學習過程以及實踐經驗,總結成文,與大家一起探討學習。本篇主要從Protobuf的基礎概念開始,包括技術背景、技術原理、使用方法和優缺點。PS:本篇本跟上篇《Protobuf從入門到精通,一篇就夠!》類似,都適合作為Protobuf的入門文章,但本篇力求簡潔,儘量不涉及Protobuf的具體技術細節,目的是降低閱讀的門檻、提升閱讀效果,希望對你有用。
圖片
學習交流:

  • 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》-
  • 開源IM框架原始碼:https://github.com/JackJiang2...(備用地址點此)

(本文已同步釋出於:http://www.52im.net/thread-40...

2、系列文章

本文是系列文章中的第 2 篇,本系列總目錄如下:
《IM通訊協議專題學習(一):Protobuf從入門到精通,一篇就夠!》
《IM通訊協議專題學習(二):快速理解Protobuf的背景、原理、使用、優缺點》(* 本文)
《IM通訊協議專題學習(三):由淺入深,從通訊編解碼原理上理解Protobuf》(稍後釋出..)《IM通訊協議專題學習(四):從Base64到Protobuf,詳解Protobuf的資料編碼原理》(稍後釋出..)
《IM通訊協議專題學習(五):Protobuf到底比JSON快幾倍?請看全方位實測!》(稍後釋出..)《IM通訊協議專題學習(六):手把手教你如何在Android上從零使用Protobuf》(稍後釋出..)《IM通訊協議專題學習(七):手把手教你如何在NodeJS中從零使用Protobuf》(稍後釋出..)《IM通訊協議專題學習(八):金蝶隨手記團隊的Protobuf應用實踐(原理篇)  》(稍後釋出..)《IM通訊協議專題學習(九):金蝶隨手記團隊的Protobuf應用實踐(實戰篇) 》(稍後釋出..)

3、什麼是Protobuf?

圖片
Protobuf(全稱是Protocol Buffers)是一種跨平臺、語言無關、可擴充套件的序列化結構資料的方法,可用於網路通訊資料交換及儲存。在序列化結構化資料的機制中,Protobuf是靈活、高效、自動化的,相對常見的XML、JSON,描述同樣的資訊,Protobuf序列化後資料量更小、序列化/反序列化速度更快、更簡單。一旦定義了要處理的資料的資料結構之後,就可以利用Protobuf的程式碼生成工具生成相關的程式碼。只需使用 Protobuf 對資料結構進行一次描述,即可利用各種不同語言(proto3支援C++, Java, Python, Go, Ruby, Objective-C, C#)或從各種不同流中對你的結構化資料輕鬆讀寫。PS:類似的介紹,在上篇《Protobuf從入門到精通,一篇就夠!》中也有涉及,有興趣可以一併閱讀之。

4、為什麼是 Protobuf?

4.1 技術背景
大家可能會覺得 Google 發明 Protobuf 是為了解決序列化速度的,其實真實的原因並不是這樣的。Protobuf最先開始是 Google用來解決索引伺服器 request/response 協議的。在沒有Protobuf之前,Google 已經存在了一種 request/response 格式,用於手動處理 request/response 的編解碼。這種sstk式也能支援多版本協議,不過程式碼不夠優雅:if(protocolVersion=1) {    doSomething();} elseif(protocolVersion=2) {    doOtherThing();} ...如果是非常明確的格式化協議,會使新協議變得非常複雜。因為開發人員必須確保請求發起者與處理請求的實際伺服器之間的所有伺服器都能理解新協議,然後才能切換開關以開始使用新協議。這也就是每個伺服器開發人員都遇到過的低版本相容、新舊協議相容相關的問題。為了解決這些問題,於是Protobuf就誕生了。
4.2 Protobuf 誕生了
Protobuf 最初被寄予以下 2 個期望:
1)更容易引入新的欄位,並且不需要檢查資料的中間伺服器可以簡單地解析並傳遞資料(而無需瞭解所有欄位);
2)資料格式更加具有自我描述性,可以用各種語言來處理(比如C++, Java 等各種語言)。
但這個版本的 Protobuf 仍需要自己手寫解析的程式碼。
隨著Protobuf的發展、演進,它具有了更多的特性:
1)自動生成的序列化和反序列化程式碼(避免了手動解析的需要。官方提供自動生成程式碼工具,各個語言平臺的基本都有);
2)除了用於資料交換之外,Protobuf也被用作某些持久化資料的便捷自描述格式。
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.
4.3 Protobuf 在谷歌業務中的地位
Protobuf 現在是 Google 用於資料交換和儲存的通用語言。谷歌程式碼樹中定義了 48162 種不同的訊息型別,包括 12183 個 .proto 檔案。它們既用於 RPC 系統,也用於在各種儲存系統中持久儲存資料。Protobuf 誕生之初是為了解決伺服器端新舊協議(高低版本)相容性問題,名字也很體貼——“協議緩衝區”,只不過後期慢慢發展成用於傳輸資料。

5、Protobuf 協議的工作原理

如下圖所示:可以看到,對於序列化協議來說,使用方只需要關注業務物件本身,即 idl 定義,序列化和反序列化的程式碼只需要透過工具生成即可。
圖片

6、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,兩個版本語法不相容,如果不指定,預設語法是proto2(由於proto3比proto2支援的語言更多,語法更簡潔,本文使用的是proto3);
2)package domain:此配置用於巢狀生成的類/物件;
3)option java_package:生成器還使用此配置來巢狀生成的源(此處的區別在於這僅適用於Java,在使用Java建立程式碼和使用JavaScript建立程式碼時,使用了兩種配置來使生成器的行為有所不同。也就是說,Java類是在包com.Protobuf.generated.domain下建立的,而JavaScript物件是在包domain下建立的)。Protobuf 提供了更多選項和資料型別,本文不做詳細介紹,感興趣可以參考官方文件。

7、Protobuf 的程式碼生成

首先安裝 Protobuf 編譯器 protoc(點這裡有詳細的安裝教程)。安裝完成後,可以使用以下命令生成 Java 原始碼:1protoc --java_out=./src/main/java./src/main/idl/customer.proto上述命令的意圖是:從專案的根路徑執行該命令,並新增了兩個引數 java_out(即定義 ./src/main/java/ 為Java程式碼的輸出目錄;而 ./src/main/idl/customer.proto 是.proto檔案所在目錄)。生成的程式碼非常複雜,但幸運的是它的用法卻非常簡單:
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());

8、Protobuf 的效能資料

我們簡單地以上述Customers為模型,分別構造、選取小物件、普通物件、大物件進行效能對比。序列化耗時以及序列化後資料大小對比:
圖片
反序列化耗時:
圖片
更多效能資料可以參考官方的測試Benchmark。

9、Protobuf 的優點

9.1效率高
從序列化後的資料體積角度,與XML、JSON這類文字協議相比,Protobuf透過 T-(L)-V(TAG-LENGTH-VALUE)方式編碼,不需要", {, }, :等分隔符來結構化資訊。同時在編碼層面使用varint壓縮。所以描述同樣的資訊,Protobuf序列化後的體積要小很多,在網路中傳輸消耗的網路流量更少,進而對於網路資源緊張、效能要求非常高的場景。比如在行動網路下的IM即時通訊應用中,Protobuf協議就是非常不錯的選擇(PS:這也是我為什麼著手分享Protobuf系列文章的原因啦)。我們來簡單做個對比。要描述如下JSON資料:1{"id":1,"firstName":"Chris","lastName":"Richardson","email":[{"type":"PROFESSIONAL","email":"crichardson@email.com"}]}使用JSON序列化後的資料大小為118byte:7b226964223a312c2266697273744e616d65223a224368726973222c226c6173744e616d65223a2252696368617264736f6e222c22656d61696c223a5b7b2274797065223a2250524f46455353494f4e414c222c22656d61696c223a226372696368617264736f6e40656d61696c2e636f6d227d5d7d而使用Protobuf序列化後的資料大小為48byte:0801120543687269731a0a52696368617264736f6e2a190a156372696368617264736f6e40656d61696c2e636f6d1001從序列化/反序列化速度角度,與XML、JSON相比,Protobuf序列化/反序列化的速度更快,比XML要快20-100倍。
9.2支援跨平臺、多語言
Protobuf是平臺無關的,無論是Android、iOS、PC,還是C#與Java,都可以利用Protobuf進行無障礙通訊。proto3支援C++、Java、Python、Go、Ruby、Objective-C、C#(詳見《Protobuf從入門到精通,一篇就夠》)。
9.3擴充套件性、相容性好
Protobuf具有向後相容的特性:更新資料結構以後,老版本依舊可以相容,這也是Protobuf誕生之初被寄予解決的問題,因為編譯器對不識別的新增欄位會跳過不處理。
9.4使用簡單
Protobuf 提供了一套編譯工具,可以自動生成序列化、反序列化的樣板程式碼,這樣開發者只要關注業務資料idl,簡化了編碼解碼工作以及多語言互動的複雜度。

10、Protobuf 的缺點

Protobuf的優點很突出,但缺點也很明顯。Protobuf的缺點主要是:
1)不具備自描述能力:跟XML、JSON相比,這兩者是自描述的,而ProtoBuf則不是;
2)資料可讀性非常差:ProtoBuf是二進位制協議,如果沒有idl檔案,就無法理解二進位制資料流,對除錯非常不友好。不過:Charles已經支援Protobuf協議,匯入資料的描述檔案即可,詳情可參考 Charles Protocol Buffers。然而:由於沒有idl檔案無法解析二進位制資料流,ProtoBuf在一定程度上可以保護資料,提升核心資料被破解的門檻,降低核心資料被盜爬的風險(也算是缺點變優點的典型範例)。

11、參考資料

[1] Protobuf官方網站
[2] Protobuf從入門到精通,一篇就夠!
[3] 如何選擇即時通訊應用的資料傳輸格式
[4] 強列建議將Protobuf作為你的即時通訊應用資料傳輸格式
[5] APP與後臺通訊資料格式的演進:從文字協議到二進位制協議
[6] 面試必考,史上最通俗大小端位元組序詳解
[7] 移動端IM開發需要面對的技術問題(含通訊協議選擇)
[8] 簡述移動端IM開發的那些坑:架構設計、通訊協議和客戶端
[9] 理論聯絡實際:一套典型的IM通訊協議設計詳解
[10] 58到家實時訊息系統的協議設計等技術實踐分享
(本文已同步釋出於:http://www.52im.net/thread-40...

相關文章