Protobuf入門和實戰

1.簡介

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

  • 使用XML表示以下:
<person>
    <name>John</name>
    <age>28</age>
    <email>jdoe@example.com</email>
  </person>
複製代碼
  • 使用JSON表示以下:
{
    name: John,
    age: 28,
    email: jdoe@example.com
}
複製代碼
  • 使用Protobuf表示以下:
message Person {
    string name = 1;
    int32 age = 2;
    string email = 3;
}
複製代碼

從可讀性和表達能力上看,XML最好,JSON其次,而Protobuf這個其實只是一個DSL,用來定義數據結構和類型,實際生成的數據是二進制的,不可讀,但Protobuf追求的是性能和速度,關於它們之間的對比,後面再說,我們先說用法。git

2.安裝環境

Protobuf的使用比較麻煩,首先須要安裝Protobuf的編譯工具(Protocol Buffers compiler),Ubuntu環境下自帶編譯環境,其它平臺可自行安裝github

jwang@jwang:~$ protoc --version
libprotoc 3.8.0
複製代碼

而後還須要安裝不一樣語言的運行環境,具體能夠參考github.com/protocolbuf…golang

3.編寫proto文件

proto實際上是一種DSL語法,這個proto文件最終會使用protoc編譯成不一樣語言的文件,而後在程序裏面調用,這也是Protobuf跨平臺的關鍵。關於proto文件的語法這裏不詳細介紹,建議你們參考官方文檔,東西不少,也很詳細。json

我這裏拿一個簡單實際的例子(person.proto)來講明一下,建議你們使用Goland安裝一個插件,這樣有顏色還能夠檢查語法:數組

  • 第一行syntax是聲明proto語法版本,若是不聲明默認是2,建議使用3版本
  • 而後是package也就包,這個影響到最後生成的go文件的包
  • 後面message是用來聲明一個數據對象,我以爲能夠理解爲結構體struct,這個數據對象有本身的數據成員,每一個字段有類型和默認值。
  • proto的數據類型有標量類型和枚舉類型,因爲不一樣語言的數據類型不太同樣,因此這裏的類型和實際語言的類型有一個對應轉換關係,具體能夠參考官方文檔
  • repeated 至關於聲明一個數組,好比在上面的例子,意思就是car是一個string類型的數組
  • message能夠嵌套聲明,也能夠引用一個類型
  • 最迷惑的東西估計就是後面那個1,2,3,4...了,據官方文檔的說法是爲了在二進制格式裏面標記數據,在每個message裏面必須是惟一的,從最小的1開始,一直能夠到2的29次方-1,也就是536870911,可是19000到19999是保留的數字。

基本語法仍是挺簡單的,不過有些深刻的用法這裏沒有介紹到,想要了解的話務必查看官方文檔,不過定義數據結構和類型只是第一步,接下來咱們還要使用protoc把這個文件編譯成對應語言的文件。bash

4.編譯proto文件

以Go語言爲例,建議切換到proto文件目錄執行命令:數據結構

protoc --go_out=. person.proto
複製代碼

其中--go_out表示輸出go版本的,其它語言把go替換就好了,好比--php_out、--java_out,=後面是須要輸出的目錄,我選擇.表示當前目錄,固然你也能夠指定輸入和輸出目錄,最後面則是須要編譯的文件,能夠指定單個文件,也可使用通配符同時編譯多個文件。

執行完命令以後,你會發現當前目錄多了一個person.pb.go文件,這是一個標準的go語法文件,裏面主要是一個結構體和一些getter函數,其它的我也不太懂是什麼意思就不說了,可是並不影響咱們使用。

5.使用

以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)
}
複製代碼

6.與JSON對比

因爲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者大小基本上差很少。

7.總結

Protobuf做爲一種新的數據交換編碼方式,雖然使用起來麻煩點,可是在性能和大小上面領先不少,能夠用來替換json,使用在一些對性能要求高的場景,好比移動端設備通訊。除此以外,目前Protobuf主要用在gRPC用做默認數據編碼格式。

相關文章
相關標籤/搜索