ProtocolBuffers学习笔记
简介
Google Protocol Buffer( 简称 Protobuf)是Google公司内部的混合语言数据标准,他们主要用于RPC系统和持续数据存储系统。
Protobuf应用场景:
Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或RPC数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
简单来说,Protobuf的功能类似于XML,即负责把某种数据结构的信息,以某种格式保存起来。主要用于数据存储、传输协议等使用场景。为什么已经有了XLM,JSON等已经很普遍的数据传输方式,还要设计出Protobuf这样一种新的数据协议呢?
Protobuf优点:
性能好/效率高
时间维度:采用XML格式对数据进行序列化时,时间消耗上性能尚可;对于使用XML格式对数据进行反序列化时的时间花费上,耗时长,性能差。
空间维度:XML格式为了保持较好的可读性,引入了一些冗余的文本信息。所以在使用XML格式进行存储数据时,也会消耗空间。
整体而言,Protobuf以高效的二进制方式存储,比XML小3到10倍,快20到100倍。
代码生成机制
代码生成机制的含义在Go语言中,可以通过定义结构体封装描述一个对象,并构造一个新的结构体对象。比如定义Person结构体,并存放于Person.go文件:
type Person struct{Name stringAge
intSex int}
在分布式系统中,因为程序代码时分开部署,比如分别为A、B。A系统在调用B系统时,无法直接采用代码的方式进行调用,因为A系统中不存在B系统中的代码。因此,A系统只负责将调用和通信的数据以二进制数据包的形式传递给B系统,由B系统根据获取到的数据包,自己构建出对应的数据对象,生成数据对象定义代码文件。这种利用编译器,根据数据文件自动生成结构体定义和相关方法的文件的机制被称作代码生成机制。
代码生成机制的优点
首先,代码生成机制能够极大解放开发者编写数据协议解析过程的时间,提高工作效率;其次,易于开发者维护和迭代,当需求发生变更时,开发者只需要修改对应的数据传输文件内容即可完成所有的修改。
支持“向后兼容”和“向前兼容”
向后兼容:在软件开发迭代和升级过程中,"后"可以理解为新版本,越新的版本越靠后;而“前”意味着早起的版本或者先前的版本。向“后”兼容即是说当系统升级迭代以后,仍然可以处理老版本的数据业务逻辑。
向前兼容:向前兼容即是系统代码未升级,但是接受到了新的数据,此时老版本生成的系统代码可以处理接收到的新类型的数据。
支持前后兼容是非常重要的一个特点,在庞大的系统开发中,往往不可能统一完成所有模块的升级,为了保证系统功能正常不受影响,应最大限度保证通讯协议的向前兼容和向后兼容。
支持多种编程语言 Protobuf不仅仅Google开源的一个数据协议,还有很多种语言的开源项目实现。在Google官方发布的Protobuf的源代码中包含了C++、Java、Python三种语言。本系列课程中,我们学习如何实现Golang语言中的功能实现。
Protobuf 缺点
可读性较差为了提高性能,Protobuf采用了二进制格式进行编码。二进制格式编码对于开发者来说,是没办法阅读的。在进行程序调试时,比较困难。
缺乏自描述 诸如XML语言是一种自描述的标记语言,即字段标记的同时就表达了内容对应的含义。而Protobuf协议不是自描述的,Protobuf是通过二进制格式进行数据传输,开发者面对二进制格式的Protobuf,没有办法知道所对应的真实的数据结构,因此在使用Protobuf协议传输时,必须配备对应的proto配置文件。
协议语法:
message:Protobuf中定义一个数据结构需要用到关键字message,这一点和Java的class,Go语言中的struct类似。标识号:在消息的定义中,每个字段等号后面都有唯一的标识号,用于在反序列化过程中识别各个字段的,一旦开始使用就不能改变。标识号从整数1开始,依次递增,每次增加1,标识号的范围为1~(2^99)-1,其中[19000-19999]为Protobuf协议预留字段,开发者不建议使用该范围的标识号;一旦使用,在编译时Protoc编译器会报出警告。
字段规则:字段规则有三种:
1、required:该规则规定,消息体中该字段的值是必须要设置的。
2、optional:消息体中该规则的字段的值可以存在,也可以为空,optional的字段可以根据defalut设置默认值。repeated:消息体中该规则字段可以存在多个(包括0个),该规则对应java的数组或者go语言的slice。
枚举类型:proto协议支持使用枚举类型,和正常的编程语言一样,枚举类型可以使用enum关键字定义在.proto文件中:
enum Age{ //1,2代表字段顺序male=1;
female=2;
}
字段默认值:.proto文件支持在进行message定义时设置字段的默认值,可以通过default进行设置,如下所示:
message Address {
required sint32 id = 1 [default = 1];
required string name = 2 [default = "北京"];
optional string pinyin = 3 [default = "beijing"];
required string address = 4;
required bool flag = 5 [default = true];
}
导入:如果需要引用的message是写在别的.proto文件中,可以通过import "xxx.proto"来进行引入:
嵌套:message与message之间可以嵌套定义,比如如下形式:
syntax = "proto2";package example;
message Person {
required string Name = 1;
required int32 Age = 2;
required string From = 3;
optional Address Addr = 4;
message Address {required sint32 id = 1;
required string name = 2;optional string pinyin = 3;
required string address = 4;}}
message更新规则
message定义以后如果需要进行修改,为了保证之前的序列化和反序列化能够兼容新的message,message的修改需要满足以下规则:
不可以修改已存在域中的标识号。
所有新增添的域必须是 optional 或者 repeated。
非required域可以被删除。但是这些被删除域的标识号不可以再次被使用。
非required域可以被转化,转化时可能发生扩展或者截断,此时标识号和名称都是不变的。
sint32和sint64是相互兼容的。
fixed32兼容sfixed32。 fixed64兼容sfixed64。
optional兼容repeated。发送端发送repeated域,用户使用optional域读取,将会读取repeated域的最后一个元素。
Protobuf 序列化后所生成的二进制消息非常紧凑,这得益于 Protobuf 采用的非常巧妙的 Encoding 方法。接下来看一看Protobuf协议是如何实现高效编码的。
编辑好proto文件后,生成命令:
route_guide.proto
将结构体赋值,并序列化:
msg_test:=&service.Order{ OrderId: proto.Int32(1),
Num: proto.Int64(1),
Timestamp: proto.String("20191030"),
}
//序列化代码
msg,err:=proto.Marshal(msg_test)//二进制
if err!=nil{
panic(err)
}
fmt.Println(msg)
反序列化操作:
//反序列化var order service.Order
err=proto.Unmarshal(msg,&order) //反序列化
if err!=nil{
panic(err)
return
}
fmt.Println(order.GetOrderId())
以上是 ProtocolBuffers学习笔记 的全部内容, 来源链接: utcz.com/z/510275.html