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