Protobuf是Google旗下的一款与平台无关,语言无关,可扩展的序列化结构数据格式。所以很适合做数据存储和作为不同应用,不同语言之间互相通信的数据交换格式,只要实现相同的协议格式即同一proto文件被编译成不同的语言版本,加入到各自的工程中去。这样不同语言就可以解析其他语言通过protobuf序列化的数据。目前官网提供了C++,Python,Java,Go等语言的支持。Google在2008年7月7日将其作为开源项目对外公布。

protobuf简介

Google Protocol Buffer(简称Protobuf)是一种轻便高效的结构化数据存储格式,平台无关,语言无关、可扩展,可用于通讯协议和数据存储等领域。

数据交互的格式比较

数据交互xml、json、protobuf格式比较

JSON:一般的web项目中,最流行的主要还是json。因为浏览器对于json数据支持非常好,有很多内建的函数支持。

XML:在webservice中应用最为广泛,但相比于json,它的数据更加冗余,因为需要成对的闭合标签。json使用了键值对的方式,不仅压缩了一定的数据空间,同时也具有可读性。

Protobuf:后起之秀,是Google开源的一种数据格式,适合高性能,对响应速度有要求的数据传输场景。因为protobuf是二进制的数据格式,需要编码和解码。数据本身不具有可读性。因此只能反序列化之后得到真正可读的数据。

相对于其它,Protobuf更有优势

  • 序列化后体积相比json和xml很小,适合网络传输
  • 支持跨平台多语言
  • 消息格式升级和兼容性还不错
  • 序列化反序列化速度快,快于json的处理速度

Protobuf的优缺点

Protobuf的优点

Protobuf有如xml,不过它更小,更快,也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。只需使用Protobuf对数据结构进行一次描述,即可利用各种不同语言或从各种不同的数据流中对你的结构化数据进行轻松读写。它有一个非常棒的特性,即“向后”兼容性好,人们不必破坏已部署的、依靠“老”数据格式的程序就可以对数据结构进行升级。Protobuf语义更清晰,无需类似xml解析器的东西。使用Protobuf无需学习复杂的文档对象模型,Protobuf的编程模式比较友好,简单易学,同时它拥有良好的文档和示例,对于喜欢简单事物的人们而言,Protobuf比其他的技术更加有吸引力。

Protobuf的不足

Protobuf与xml相比也有不足之处。它功能简单,无法用来表示复杂的概念。xml已经成为多种行业标准的编写工具,Protobuf只是Google公司内部使用的工具,在通用性上还差很多。由于文本并不适合用来描述数据结构,所以Protobuf也不适合用来对基于文本的标记文档建模。另外,由于xml具有某种程度上的自解释性,它可以被人直接读取编辑,在这一点上Protobuf不行,它以二进制的方式存储,除非你有.proto定义,否则你无法直接读出Protobuf的任何内容。

Protobuf安装

安装protoc

可以上https://github.com/google/protobuf/releases下载对应平台的二进制版本,并将protoc放到PATH环境变量中。

安装完执行protoc --version验证安装

1protoc --version
2libprotoc 3.11.4

安装proto包

1go get -u github.com/golang/protobuf/proto

安装protoc-gen-go插件

1go get -u github.com/golang/protobuf/protoc-gen-go

执行完会在$GOPATH/bin目录下生成二进制protoc-gen-go文件

Protobuf基本语法

要想使用protobuf必须先定义proto文件。所以需要先数据Protobuf的消息定义的相关语法。

定义一个消息类型

 1syntax = "proto3";
 2
 3// 请求
 4message PandaRequest {
 5  // 姓名
 6	string name = 1;
 7	// 身高
 8	int32 height = 2;
 9	// 体重
10	repeated int32 weight =3;
11}
12
13// 响应
14message PandaResponse{
15	// 错误号
16	int32 err = 1;
17	// 错误信息
18	string errmessage = 2;
19}

文件的第一行指定了你正在使用proto3的语法;如果没有指定,编译器默认使用proto2,指定语法必须是文件的非空非注释的首行。上面的例子中,所有字段都是标量类型,两个整型一个string类型。repeated关键字表示重复的在go语言中用切片来代表。每个字段都有唯一的标识符。

当用protobuf编译器来运行.proto文件时,编译器将生成所选择语言的代码,这些代码可以操作在.proto文件中定义的消息类型,包括获取,设置字段值,将消息序列化到一个输出流中,以及从一个输入流中解析消息。

protobuf类型

.proto Type Notes Go Type
double float64
float float32
int32 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. int32
int64 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. int64
uint32 Uses variable-length encoding. uint32
uint64 Uses variable-length encoding. uint64
sint32 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. int32
sint64 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. int64
fixed32 Always four bytes. More efficient than uint32 if values are often greater than 228. uint32
fixed64 Always eight bytes. More efficient than uint64 if values are often greater than 256. uint64
sfixed32 Always four bytes. int32
sfixed64 Always eight bytes. int64
bool bool
string A string must always contain UTF-8 encoded or 7-bit ASCII text, and cannot be longer than 232. string
bytes May contain any arbitrary sequence of bytes no longer than 232. []byte

定义服务

如果想要将消息类型用在rpc系统中,可以在.proto文件中定义一个rpc服务接口,protobuf编译器将会根据所选择的语言生成服务结构代码以及存根。如想要定义一个rpc服务并具有一个方法,该方法能够接收SearchRequest并返回一个SearchResponse,此时可以在.proto中进行如下定义:

1service SearchService {
2	rpc Search (SearchRequest) returns (SearchResponse);
3}

生成访问类

1protoc --go_out ./ path/to/file.proto

执行完会生成一个.pb.go的文件,上例中的代码生成的go文件内容如下:

  1// Code generated by protoc-gen-go. DO NOT EDIT.
  2// source: proto/panda.proto
  3
  4package panda
  5
  6import (
  7	fmt "fmt"
  8	proto "github.com/golang/protobuf/proto"
  9	math "math"
 10)
 11
 12// Reference imports to suppress errors if they are not otherwise used.
 13var _ = proto.Marshal
 14var _ = fmt.Errorf
 15var _ = math.Inf
 16
 17// This is a compile-time assertion to ensure that this generated file
 18// is compatible with the proto package it is being compiled against.
 19// A compilation error at this line likely means your copy of the
 20// proto package needs to be updated.
 21const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
 22
 23// 请求
 24type PandaRequest struct {
 25	// 姓名
 26	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
 27	// 身高
 28	Height int32 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"`
 29	// 体重
 30	Weight               []int32  `protobuf:"varint,3,rep,packed,name=weight,proto3" json:"weight,omitempty"`
 31	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 32	XXX_unrecognized     []byte   `json:"-"`
 33	XXX_sizecache        int32    `json:"-"`
 34}
 35
 36func (m *PandaRequest) Reset()         { *m = PandaRequest{} }
 37func (m *PandaRequest) String() string { return proto.CompactTextString(m) }
 38func (*PandaRequest) ProtoMessage()    {}
 39func (*PandaRequest) Descriptor() ([]byte, []int) {
 40	return fileDescriptor_3314ebc5778d4431, []int{0}
 41}
 42
 43func (m *PandaRequest) XXX_Unmarshal(b []byte) error {
 44	return xxx_messageInfo_PandaRequest.Unmarshal(m, b)
 45}
 46func (m *PandaRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
 47	return xxx_messageInfo_PandaRequest.Marshal(b, m, deterministic)
 48}
 49func (m *PandaRequest) XXX_Merge(src proto.Message) {
 50	xxx_messageInfo_PandaRequest.Merge(m, src)
 51}
 52func (m *PandaRequest) XXX_Size() int {
 53	return xxx_messageInfo_PandaRequest.Size(m)
 54}
 55func (m *PandaRequest) XXX_DiscardUnknown() {
 56	xxx_messageInfo_PandaRequest.DiscardUnknown(m)
 57}
 58
 59var xxx_messageInfo_PandaRequest proto.InternalMessageInfo
 60
 61func (m *PandaRequest) GetName() string {
 62	if m != nil {
 63		return m.Name
 64	}
 65	return ""
 66}
 67
 68func (m *PandaRequest) GetHeight() int32 {
 69	if m != nil {
 70		return m.Height
 71	}
 72	return 0
 73}
 74
 75func (m *PandaRequest) GetWeight() []int32 {
 76	if m != nil {
 77		return m.Weight
 78	}
 79	return nil
 80}
 81
 82// 响应
 83type PandaResponse struct {
 84	// 错误号
 85	Err int32 `protobuf:"varint,1,opt,name=err,proto3" json:"err,omitempty"`
 86	// 错误信息
 87	Errmessage           string   `protobuf:"bytes,2,opt,name=errmessage,proto3" json:"errmessage,omitempty"`
 88	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 89	XXX_unrecognized     []byte   `json:"-"`
 90	XXX_sizecache        int32    `json:"-"`
 91}
 92
 93func (m *PandaResponse) Reset()         { *m = PandaResponse{} }
 94func (m *PandaResponse) String() string { return proto.CompactTextString(m) }
 95func (*PandaResponse) ProtoMessage()    {}
 96func (*PandaResponse) Descriptor() ([]byte, []int) {
 97	return fileDescriptor_3314ebc5778d4431, []int{1}
 98}
 99
100func (m *PandaResponse) XXX_Unmarshal(b []byte) error {
101	return xxx_messageInfo_PandaResponse.Unmarshal(m, b)
102}
103func (m *PandaResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
104	return xxx_messageInfo_PandaResponse.Marshal(b, m, deterministic)
105}
106func (m *PandaResponse) XXX_Merge(src proto.Message) {
107	xxx_messageInfo_PandaResponse.Merge(m, src)
108}
109func (m *PandaResponse) XXX_Size() int {
110	return xxx_messageInfo_PandaResponse.Size(m)
111}
112func (m *PandaResponse) XXX_DiscardUnknown() {
113	xxx_messageInfo_PandaResponse.DiscardUnknown(m)
114}
115
116var xxx_messageInfo_PandaResponse proto.InternalMessageInfo
117
118func (m *PandaResponse) GetErr() int32 {
119	if m != nil {
120		return m.Err
121	}
122	return 0
123}
124
125func (m *PandaResponse) GetErrmessage() string {
126	if m != nil {
127		return m.Errmessage
128	}
129	return ""
130}
131
132func init() {
133	proto.RegisterType((*PandaRequest)(nil), "PandaRequest")
134	proto.RegisterType((*PandaResponse)(nil), "PandaResponse")
135}
136
137func init() {
138	proto.RegisterFile("proto/panda.proto", fileDescriptor_3314ebc5778d4431)
139}
140
141var fileDescriptor_3314ebc5778d4431 = []byte{
142	// 151 bytes of a gzipped FileDescriptorProto
143	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2c, 0x28, 0xca, 0x2f,
144	0xc9, 0xd7, 0x2f, 0x48, 0xcc, 0x4b, 0x49, 0xd4, 0x03, 0xb3, 0x95, 0x82, 0xb8, 0x78, 0x02, 0x40,
145	0xdc, 0xa0, 0xd4, 0xc2, 0xd2, 0xd4, 0xe2, 0x12, 0x21, 0x21, 0x2e, 0x96, 0xbc, 0xc4, 0xdc, 0x54,
146	0x09, 0x46, 0x05, 0x46, 0x0d, 0xce, 0x20, 0x30, 0x5b, 0x48, 0x8c, 0x8b, 0x2d, 0x23, 0x35, 0x33,
147	0x3d, 0xa3, 0x44, 0x82, 0x49, 0x81, 0x51, 0x83, 0x35, 0x08, 0xca, 0x03, 0x89, 0x97, 0x43, 0xc4,
148	0x99, 0x15, 0x98, 0x41, 0xe2, 0x10, 0x9e, 0x92, 0x23, 0x17, 0x2f, 0xd4, 0xcc, 0xe2, 0x82, 0xfc,
149	0xbc, 0xe2, 0x54, 0x21, 0x01, 0x2e, 0xe6, 0xd4, 0xa2, 0x22, 0xb0, 0x99, 0xac, 0x41, 0x20, 0xa6,
150	0x90, 0x1c, 0x17, 0x57, 0x6a, 0x51, 0x51, 0x6e, 0x6a, 0x71, 0x71, 0x62, 0x7a, 0x2a, 0xd8, 0x58,
151	0xce, 0x20, 0x24, 0x91, 0x24, 0x36, 0xb0, 0xeb, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x84,
152	0x8c, 0xf0, 0x38, 0xb2, 0x00, 0x00, 0x00,
153}

参考

https://developers.google.com/protocol-buffers/docs/proto3