Golang中使用protobuf高效编码

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

定义 *.proto

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还没有正式版,虽然官方已经发布正式版)

brew install protobuf --devel

安装Golang代码生成器插件

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

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

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 代码

官方用法

protoc --go_out=. *.proto

gogo优化用法

protoc --gofast_out=. *.proto

使用 *.pb.go

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编码后的消息

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
}

参考: