ProtoBuf 做爲一種跨平臺、語言無關、可擴展的序列化結構數據的方法,已普遍應用於網絡數據交換及存儲。隨着互聯網的發展,系統的異構性會愈發突出,跨語言的需求會越發明顯,同時 gRPC 也大有取代Restful之勢,而 ProtoBuf 做爲g RPC 跨語言、高性能的法寶,咱們技術人有必要html
深刻理解 ProtoBuf 原理,爲之後的技術更新和選型打下基礎。java
我將過去的學習過程以及實踐經驗,總結成系列文章,與你們一塊兒探討學習,但願你們能有所收穫,固然其中有不正確的地方也歡迎你們批評指正。服務器
本系列文章主要包含:網絡
ProtoBuf(Protocol Buffers)是一種跨平臺、語言無關、可擴展的序列化結構數據的方法,可用於網絡數據交換及存儲。數據結構
在序列化結構化數據的機制中,ProtoBuf是靈活、高效、自動化的,相對常見的XML、JSON,描述一樣的信息,ProtoBuf序列化後數據量更小、序列化/反序列化速度更快、更簡單。dom
一旦定義了要處理的數據的數據結構以後,就能夠利用ProtoBuf的代碼生成工具生成相關的代碼。只需使用 Protobuf 對數據結構進行一次描述,便可利用各類不一樣語言(proto3支持C++, Java, Python, Go, Ruby, Objective-C, C#)或從各類不一樣流中對你的結構化數據輕鬆讀寫。ide
你們可能會以爲 Google 發明 ProtoBuf 是爲了解決序列化速度的,其實真實的緣由並非這樣的。工具
ProtoBuf最早開始是 Google用來解決索引服務器 request/response 協議的。沒有ProtoBuf以前,Google 已經存在了一種 request/response 格式,用於手動處理 request/response 的編解碼。它也能支持多版本協議,不過代碼不夠優雅:性能
if (protocolVersion=1) { doSomething(); } else if (protocolVersion=2) { doOtherThing(); } ...
若是是很是明確的格式化協議,會使新協議變得很是複雜。由於開發人員必須確保請求發起者與處理請求的實際服務器之間的全部服務器都能理解新協議,而後才能切換開關以開始使用新協議。學習
這也就是每一個服務器開發人員都遇到過的低版本兼容、新舊協議兼容相關的問題。
爲了解決這些問題,因而ProtoBuf就誕生了。
ProtoBuf 最初被寄予如下 2 個特色:
這個版本的 ProtoBuf 仍須要本身手寫解析的代碼。
不過隨着系統慢慢發展,演進,ProtoBuf具備了更多的特性:
ProtoBuf 如今是 Google 用於數據交換和存儲的通用語言。谷歌代碼樹中定義了 48162 種不一樣的消息類型,包括 12183 個 .proto 文件。它們既用於 RPC 系統,也用於在各類存儲系統中持久存儲數據。
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.
能夠看到,對於序列化協議來講,使用方只須要關注業務對象自己,即 idl 定義,序列化和反序列化的代碼只須要經過工具生成便可。
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的集合。
除了這些定義外,文件頂部還有三行可幫助代碼生成器:
首先,syntax = "proto3"用於idl語法版本,目前有兩個版本proto2和proto3,兩個版本語法不兼容,若是不指定,默認語法是proto2。因爲proto3比proto2支持的語言更多,語法更簡潔,本文使用的是proto3。
其次有一個package domain;定義。此配置用於嵌套生成的類/對象。
ProtoBuf 提供了更多選項和數據類型,本文不作詳細介紹,感興趣能夠參考這裏。
首先安裝 ProtoBuf 編譯器 protoc,這裏有詳細的安裝教程,安裝完成後,可使用如下命令生成 Java 源代碼:
protoc --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());
咱們簡單地以Customers爲模型,分別構造、選取小對象、普通對象、大對象進行性能對比。
序列化耗時以及序列化後數據大小對比
反序列化耗時
更多性能數據能夠參考官方 Benchmark
上面介紹了 ProtoBuf 是什麼、產生的背景、基本用法,咱們再總結下。
優勢:
1. 效率高
從序列化後的數據體積角度,與XML、JSON這類文本協議相比,ProtoBuf經過T-(L)-V(TAG-LENGTH-VALUE)方式編碼,不須要", {, }, :等分隔符來結構化信息,同時在編碼層面使用varint壓縮,因此描述一樣的信息,ProtoBuf序列化後的體積要小不少,在網絡中傳輸消耗的網絡流量更少,進而對於網絡資源緊張、性能要求很是高的場景,ProtoBuf協議是不錯的選擇。
// 咱們簡單作個對比 // 要描述以下JSON數據 {"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倍。
2. 支持跨平臺、多語言
ProtoBuf是平臺無關的,不管是Android與PC,仍是C#與Java均可以利用ProtoBuf進行無障礙通信。
proto3支持C++, Java, Python, Go, Ruby, Objective-C, C#。
3. 擴展性、兼容性好
具備向後兼容的特性,更新數據結構之後,老版本依舊能夠兼容,這也是ProtoBuf誕生之初被寄予解決的問題。由於編譯器對不識別的新增字段會跳過不處理。
4. 使用簡單
ProtoBuf 提供了一套編譯工具,能夠自動生成序列化、反序列化的樣板代碼,這樣開發者只要關注業務數據idl,簡化了編碼解碼工做以及多語言交互的複雜度。
缺點:
可讀性差,缺少自描述
XML,JSON是自描述的,而ProtoBuf則不是。
ProtoBuf是二進制協議,編碼後的數據可讀性差,若是沒有idl文件,就沒法理解二進制數據流,對調試不友好。
不過Charles已經支持ProtoBuf協議,導入數據的描述文件便可,詳情可參考Charles Protocol Buffers
此外,因爲沒有idl文件沒法解析二進制數據流,ProtoBuf在必定程度上能夠保護數據,提高核心數據被破解的門檻,下降核心數據被盜爬的風險。
做者: Li Guanyun