Protobuf ====================== ###### tags: `gRPC` ```bash protoc -I. common.proto --go_out=. country.proto protoc -I . --go_out=. common.proto country.proto ``` ## 安裝 prtoc-gen for go ```bash go get github.com/golang/protobuf/protoc-gen-go ``` prtoc install ```bash sudo apt-get install autoconf automake libtool curl make g++ unzip -y git clone https://github.com/google/protobuf.git cd protobuf git submodule update --init --recursive ./autogen.sh ./configure --prefix=/usr make && make check sudo make install sudo ldconfig ``` 按照上述命令安装ProtoBuf后,路径/usr/include/google/protobuf下会包含一些proto文件。 ## 編譯 build ```bash protoc --proto_path=src --go_out=build/gen src/a.proto src/bar/b.proto ``` 在/src下的`.proto`檔都會被編譯成`.pb.go`檔. --- # Protobuf protobuf 全名是protocol buffers. protobuf是一種對於結構體, 具有彈性、高效、自動化的描述方式. 相比於XML更小更簡單. 寫好的proto檔, 可以用來生成各種對應語言的資料, 還會生成寫入和讀取結構的方法. ## 相比於XML 1. Easy 2. Smaller 3 to 10 times 3. Faster 20 to 100 times ## 相比於JSON 1. Smaller 2. Faster ### compare size example `person.proto` ```proto syntax = "proto3"; package main; message Person { int32 id=1; string name = 2; string email = 3; } ``` `person.xml` ```xml <person> <Id>1</Id> <name>John Doe</name> <email>jdoe@example.com</email> </person> ``` ```go package main import ( "io/ioutil" "os" "encoding/json" "encoding/xml" "fmt" proto "github.com/golang/protobuf/proto" "github.com/vmihailenco/msgpack/v4" ) type XmlPerson struct { XMLName xml.Name `xml:"person" ` Text string `xml:",chardata" ` Id int `xml:"Id"` Name string `xml:"name"` Email string `xml:"email"` } type JsonPerson struct { Id int `json:"id"` Name string `xml:"name" json:"name"` Email string `xml:"email" json:"email"` } func main() { /* xml */ xmlFile, err := os.Open("person.xml") if err != nil { fmt.Println(err) } defer xmlFile.Close() byteValue, _ := ioutil.ReadAll(xmlFile) var xmlPerson XmlPerson xml.Unmarshal(byteValue, &xmlPerson) fmt.Printf("size of xml : %d\n", len(byteValue)) /* protobuf */ var protoPerson Person = Person{ Id: 1, Name: "John Doe", Email: "jdoe@example.com", } protoByteValue, err := proto.Marshal(&protoPerson) fmt.Printf("size of protobuf : %d\n", len(protoByteValue)) /* json */ var jsonPerson JsonPerson = JsonPerson{ Id: 1, Name: "John Doe", Email: "jdoe@example.com", } jsonByteValue, _ := json.Marshal(&jsonPerson) fmt.Printf("size of json : %d\n", len(jsonByteValue)) /* message pack */ var mpPerson JsonPerson = JsonPerson{ Id: 1, Name: "John Doe", Email: "jdoe@example.com", } mpByteValue, _ := msgpack.Marshal(&mpPerson) fmt.Printf("size of message pack : %d\n", len(mpByteValue)) fmt.Println("done") } ``` ``` size of xml : 99 size of protobuf : 30 size of json : 53 size of message pack : 50 done ``` Size是Protobuf最小, 其次是Json, 最肥的是XML. ### Marshal and Unmarshal Performance Compare `xml_test.go` ```go package main import ( "encoding/xml" "io/ioutil" "os" "testing" ) type XmlPerson struct { XMLName xml.Name `xml:"person" ` Text string `xml:",chardata" ` Id int `xml:"Id"` Name string `xml:"name"` Email string `xml:"email"` } func BenchmarkXMLUnmarshal(b *testing.B) { xmlFile, _ := os.Open("test.xml") defer xmlFile.Close() byteValue, _ := ioutil.ReadAll(xmlFile) b.ResetTimer() for i := 0; i < b.N; i++ { var xmlPerson XmlPerson xml.Unmarshal(byteValue, &xmlPerson) } } func BenchmarkXMLMarshal(b *testing.B) { var target XmlPerson = XmlPerson{ ID: 1, Name: "John Doe", Email: "jdoe@example.com", } b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = xml.Marshal(target) } } ``` `protobuf_test.go` ```gopackage main import ( "testing" "github.com/golang/protobuf/proto" ) func BenchmarkProtobufUnmarshal(b *testing.B) { var target Person = Person{ Id: 1, Name: "John Doe", Email: "jdoe@example.com", } bytesData, _ := proto.Marshal(&target) b.ResetTimer() for i := 0; i < b.N; i++ { var person Person proto.Unmarshal(bytesData, &person) } } func BenchmarkProtobufMarshal(b *testing.B) { var target Person = Person{ Id: 1, Name: "John Doe", Email: "jdoe@example.com", } b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = proto.Marshal(&target) } } ``` `json_test.go` ```go package main import ( "encoding/json" "testing" ) type Person struct { Id int `json:"id"` Name string `xml:"name" json:"name"` Email string `xml:"email" json:"email"` } func BenchmarkJsonUnmarshal(b *testing.B) { var target Person = Person{ Id: 1, Name: "John Doe", Email: "jdoe@example.com", } bytesData, _ := json.Marshal(&target) b.ResetTimer() for i := 0; i < b.N; i++ { var person Person json.Unmarshal(bytesData, &person) } } func BenchmarkJsonMarshal(b *testing.B) { var target Person = Person{ Id: 1, Name: "John Doe", Email: "jdoe@example.com", } b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = json.Marshal(target) } } ``` `messagepack_test.go` ```go package main import ( "testing" "github.com/vmihailenco/msgpack/v4" ) type Person struct { Id int Name string Email string } func BenchmarkMessagePackUnmarshal(b *testing.B) { var target Person = Person{ Id: 1, Name: "John Doe", Email: "jdoe@example.com", } bytesData, _ := msgpack.Marshal(&target) b.ResetTimer() for i := 0; i < b.N; i++ { var person Person msgpack.Unmarshal(bytesData, &person) } } func BenchmarkMessagePackMarshal(b *testing.B) { var target Person = Person{ Id: 1, Name: "John Doe", Email: "jdoe@example.com", } b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = msgpack.Marshal(&target) } } ``` 跑個測試, 預設測試是1 second ```bash go test -v -bench=. ``` #### XML ``` BenchmarkXMLUnmarshal-4 200000 7116 ns/op BenchmarkXMLMarshal-4 500000 2523 ns/op ``` #### JSON ``` BenchmarkJsonUnmarshal-4 1000000 1293 ns/op BenchmarkJsonMarshal-4 3000000 449 ns/op ``` ### MessagePack ``` BenchmarkMessagePackUnmarshal-8 2000000 821 ns/op BenchmarkMessagePackMarshal-8 3000000 572 ns/op ``` ### Protobuf ``` BenchmarkProtobufUnmarshal-4 10000000 194 ns/op BenchmarkProtobufMarshal-4 10000000 159 ns/op ``` Protobuf不管在Marshal/Unmarshal 一次操作大概都是150-200 ns. XML是怎樣都很慢, Json是慢在Unmarshal. # Protobuf 撰寫 ## Define protobuf version 現在protobuf來到第3版與第2版並不兼容 ```protobuf syntax = "proto3"; ``` ## Iport package 匯入同專案下其他的proto檔 ```protobuf import "other_protos.proto"; ``` ## Define package 定義轉成對應語言的package名稱 ```protobuf pakcage foo.bar; ``` ### Option package name 定義指定語言的package轉出名稱 ```protobuf option go_package= "foo"; option java_package= "com.example.foo"; ``` ## Define message struct message 就像Go的struct一樣, 用來定義struct跟裡面的property ```protobuf massage helloworld { string query = 1; int32 id = 2; } ``` ### Nested message