Protobuf(Google Protocol Buffer)是 Google公司內部的混合語言數據標準,目前已經開源,支持多種語言(C++、C#、Go、JS、Java、Python、PHP),它是一種輕便高效的結構化數據存儲格式,能夠用於結構化數據串行化,或者說序列化。它很適合作數據存儲或 RPC 數據交換格式。可用於通信協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。php
說簡單點,Protobuf就是相似JSON、XML這樣的數據交換格式,當今互聯網JSON是最流行的格式了,XML用的也挺多,最初接觸到Protobuf是由於gRPC默認使用它做爲數據編碼,相比於JSON和XML,它更小,更快!java
舉個例子:若是咱們想表達一我的名字叫John,年齡是28歲,郵箱是jdoe@gmail.com這樣的結構化數據,而且須要在互聯網上傳輸nginx
<person>
<name>John</name>
<age>28</age>
<email>jdoe@example.com</email>
</person>
複製代碼
{
name: John,
age: 28,
email: jdoe@example.com
}
複製代碼
message Person {
string name = 1;
int32 age = 2;
string email = 3;
}
複製代碼
從可讀性和表達能力上看,XML最好,JSON其次,而Protobuf這個其實只是一個DSL,用來定義數據結構和類型,實際生成的數據是二進制的,不可讀,但Protobuf追求的是性能和速度,關於它們之間的對比,後面再說,我們先說用法。git
Protobuf的使用比較麻煩,首先須要安裝Protobuf的編譯工具(Protocol Buffers compiler),Ubuntu環境下自帶編譯環境,其它平臺可自行安裝github
jwang@jwang:~$ protoc --version
libprotoc 3.8.0
複製代碼
而後還須要安裝不一樣語言的運行環境,具體能夠參考github.com/protocolbuf…golang
proto實際上是一種DSL語法,這個proto文件最終會使用protoc編譯成不一樣語言的文件,而後在程序裏面調用,這也是Protobuf跨平臺的關鍵。關於proto文件的語法這裏不詳細介紹,建議你們參考官方文檔,東西不少,也很詳細。json
我這裏拿一個簡單實際的例子(person.proto)來講明一下,建議你們使用Goland安裝一個插件,這樣有顏色還能夠檢查語法:數組
基本語法仍是挺簡單的,不過有些深刻的用法這裏沒有介紹到,想要了解的話務必查看官方文檔,不過定義數據結構和類型只是第一步,接下來咱們還要使用protoc把這個文件編譯成對應語言的文件。bash
以Go語言爲例,建議切換到proto文件目錄執行命令:數據結構
protoc --go_out=. person.proto
複製代碼
其中--go_out表示輸出go版本的,其它語言把go替換就好了,好比--php_out、--java_out,=後面是須要輸出的目錄,我選擇.表示當前目錄,固然你也能夠指定輸入和輸出目錄,最後面則是須要編譯的文件,能夠指定單個文件,也可使用通配符同時編譯多個文件。
執行完命令以後,你會發現當前目錄多了一個person.pb.go文件,這是一個標準的go語法文件,裏面主要是一個結構體和一些getter函數,其它的我也不太懂是什麼意思就不說了,可是並不影響咱們使用。
以Go爲例,咱們須要安裝一個運行庫,其它語言也差很少,官方針對每個語言都有一個單獨的介紹文檔,務必查閱一下。
下面是一個完整的案例:
package main
import (
"fmt"
"github.com/golang/Protobuf/proto"
"io/ioutil"
"os"
)
func main() {
//實例化模型對象,填充數據
p := &Person{
Id: 1,
Name: "jun",
Age: 25,
Money: 24.5,
Car: []string{"car1", "car2"},
Phone: &Person_Phone{Number: "0551-12323232", Type: "1"},
Sex: Person_female,
}
//Marshal序列化
out, err := proto.Marshal(p)
if err != nil {
panic(err)
}
//序列化獲得結果是二進制的,是不可讀的,因此這裏保存到文件
file, _ := os.OpenFile("out", os.O_CREATE|os.O_WRONLY, 0666)
_, _ = file.Write(out)
_ = file.Close()
//unMarshal還原數據,從文件裏面讀取
in, _ := os.Open("out")
bytes, err := ioutil.ReadAll(in)
if err != nil {
panic(err)
}
p1 := &Person{}
err = proto.Unmarshal(bytes, p1)
if err != nil {
panic(err)
}
//調用string()方法打印,也可使用其生成的getter函數
fmt.Printf("%s\n", p1.String())
fmt.Printf("%d\n", p1.GetId)
}
複製代碼
因爲XML目前不多使用在Web API接口上,因此這裏就不對比了,主要看一下和JSON的對比,包含2個方面:速度和大小。
爲了測試,我在proto文件裏面又加了一個數據對象,表示一個組裏面有多個person對象
message Group {
repeated Person person = 1;
}
複製代碼
分別測試有1,10,100個對象的時候對比狀況,測試代碼以下:
func BenchmarkProto(b *testing.B) {
g := &Group{}
for i := 0; i < 100; i++ {
p := &Person{
Id: int32(i),
Name: "測試名稱",
Age: int32(25 * i),
Money: 240000.5,
Car: []string{"car1", "car2", "car3", "car4", "car5", "car7", "car6", "car21", "car22",},
Phone: &Person_Phone{Number: "0551-12323232", Type: "1"},
Sex: Person_female,
}
g.Person = append(g.Person, p)
}
b.ResetTimer()
b.N = 1000
for i := 0; i < b.N; i++ {
out, err := proto.Marshal(g)
if err != nil {
panic(err)
}
g1 := &Group{}
err = proto.Unmarshal(out, g1)
if err != nil {
panic(err)
}
}
}
func BenchmarkJson(b *testing.B) {
g := &Group{}
for i := 0; i < 100; i++ {
p := &Person{
Id: int32(i),
Name: "測試名稱",
Age: int32(25 * i),
Money: 240000.5,
Car: []string{"car1", "car2", "car3", "car4", "car5", "car7", "car6", "car21", "car22",},
Phone: &Person_Phone{Number: "0551-12323232", Type: "1"},
Sex: Person_female,
}
g.Person = append(g.Person, p)
}
b.ResetTimer()
b.N = 1000
for i := 0; i < b.N; i++ {
out, err := json.Marshal(g)
if err != nil {
panic(err)
}
g1 := &Group{}
err = json.Unmarshal(out, g1)
if err != nil {
panic(err)
}
}
}
複製代碼
爲了方便對比,指定了測試次數爲1000次,測試結果以下:
在1個person的級別:
能夠看出,理論上proto明顯比json要快很多,每次操做大概是4-5倍差距。後面在10,100個person的級別的測試中,基本上都是保持在4-5倍性能的差距,這個結果也和網上大部分測試結果一致。
關於生成的數據大小,這裏也簡單測試了一遍,仍是上面的例子,我使用了10個person,Protobuf生成的文件大小是1030個byte,json生成的文件大小是1842個byte。
須要注意一點,雖然在大小上Protobuf也領先不少,可是據網上文章介紹,在通過nginx的gzip壓縮以後,這2者大小基本上差很少。
Protobuf做爲一種新的數據交換編碼方式,雖然使用起來麻煩點,可是在性能和大小上面領先不少,能夠用來替換json,使用在一些對性能要求高的場景,好比移動端設備通訊。除此以外,目前Protobuf主要用在gRPC用做默認數據編碼格式。