Golang中使用protobuf高效编码

Protocol Buffers 是Google推出的一种数据交换格式。二进制、高性能、跨平台、跨编程语言。很适合现在分布式、微服务中结合消息总线做高性能数据交换。

定义 *.proto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
syntax = "proto3";
package tutorial;

message Person {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;

enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}

message PhoneNumber {
string number = 1;
PhoneType type = 2;
}

repeated PhoneNumber phones = 4;
}

// Our address book file is just one of these.
message AddressBook {
repeated Person people = 1;
}

编译 *.proto

先安装protobuf 3.0 C++实现 (目前mac上brew还没有正式版,虽然官方已经发布正式版)

1
brew install protobuf --devel

安装Golang代码生成器插件

官方的
go get -u github.com/golang/protobuf/protoc-gen-go

gogo优化版(快5-7倍,性能对比

1
2
3
go get github.com/gogo/protobuf/proto
go get github.com/gogo/protobuf/protoc-gen-gogo
go get github.com/gogo/protobuf/gogoproto

生成Golang *.pb.go 代码

官方用法

1
protoc --go_out=. *.proto

gogo优化用法

protoc --gofast_out=. *.proto

使用 *.pb.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import (
"fmt"
"log"

"github.com/gogo/protobuf/proto"

pb "demo/protobuf/tutorial"
)

func main() {
p := &pb.Person{
Id: 1234,
Name: "Jerry Hou",
Email: "https@yryz.net",
Phones: []*pb.Person_PhoneNumber{
{Number: "110", Type: pb.Person_HOME},
{Number: "911", Type: pb.Person_WORK},
},
}

// 编码
out, err := proto.Marshal(p)
if err != nil {
log.Fatal("failed to marshal: ", err)
}

// 解码
p2 := &pb.Person{}
if err := proto.Unmarshal(out, p2); err != nil {
log.Fatal("failed to unmarshal: ", err)
}

fmt.Println(p2)
fmt.Println(out)
fmt.Println(string(out))

}

在网络编程中使用

需要自己再封装消息长度(使用 nsq这类消息总线可以省略)、消息类型、protobuf编码后的消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package protocol

// 网络消息类型
type MsgType uint8

const (
CommonMsgType MsgType = 0 // 用于解码
ScanRequestMsg MsgType = 1 // 扫描请求
ScanResponseMsg MsgType = 2 // 扫描数据响应
ScanDoneMsg MsgType = 3 // 扫描完成
)

// 编码网络消息
func (m MsgType) Encode(pbMsg []byte) []byte {
// b = make([]byte, len(pbMsg)+unsafe.Sizeof(*m))
return append([]byte{byte(m)}, pbMsg...)
}

// 解码网络消息
func (m MsgType) Decode(msg []byte) (mt MsgType, pbMsg []byte) {
mt = MsgType(msg[0])
pbMsg = msg[1:]
return
}