Protocol Buffers 入门详解

Protocol Buffers 入门详解

1. 概念

1.1 What?(什么是Protocol Buffers?)

  Protocol Buffers(后面简称protobuf)是google团队开发的一种语言中立,平台无关,可扩展的数据压缩编码方式(序列化),其很适合作数据存储或RPC数据交换格式。可用于通信协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。ide

1.2 Why?(为何使用Protocol Buffers?)

  Protobuf的存在有两个缘由,一个是为了从数据存储的角度来加速站点间数据传输速度,另外一个是为了解决站点间数据传输的协议不规范问题。
传输数据的大小无疑是影响传输速度的关键因素,当前流行的数据传输协议(json、xml等)会携带一些“结构化”的数据(如标签等),另外,它们对数据的压缩也没有很极致,因此须要有一个对数据存储很“紧凑”,对数据压缩很高效的工具来进行革新。工具

  最初的数据传输协议是request/response形式的,没有 protocol buffers 以前,google 已经存在了一种 request/response 格式,用于手动处理 request/response 的编码和反编码。可是这种协议每每没有很明确的格式,因此开发人员常常会遇到新旧版协议不兼容的问题。所以,急需一个协议不须要了解全部业务字段还能灵活地应对各类改动的需求。性能

1.3 How?(Protocol Buffers 是怎么作到的?)

  protobuf对传输的数据采起一种最简单的key-value形式的存储方式(但其中有一种类型的数据不是k-v形式,后面会讲),这钟存储方式极大的节省了空间。除此以外protobuf还采起了varint(变长编码)形式来压缩数据,对体积较小的字段分配较少的空间,由此使得压缩后的文件很是“紧凑”。

  protobuf定义了一种后缀名为“.proto”的描述型文件为待传输的结构化数据做为数据协议,待传输的数据必须符合“.proto”文件中的相关定义。“.proto”文件在简洁易读的同时很好地保留了原数据的结构信息,而且还给出了一些实用的关键字,灵活地让开发者对数据中的字段作选取,给了开发人员很大的发挥空间,基本解决了其余协议出现的被需求牵着走的局面。“.proto”文件能够看做一种数据传输的协议,须要开发人员按照语法编写,其还能够经过protobuf工具编译成C++/Java/Python对象。

2.protobuf文件中的语法规范

2.1 message结构

  在“.proto”文件中protobuf将一种结构称为一个message,相似Java中的一个Entity对象,咱们以电话簿中的数据为例:
avatar
  其中Person表明这个结构体的名字,name、id、email都是结构体中的字段,咱们称之为field。要注意的是,等号右边赋值的不是value,而是字段field对应的field id。Field最前面的required、optional、repeated是这个Filed的规则,分别表示该数据结构中这个Filed有且只有1个,能够是0个或1个,能够是0个或任意个。optional后面能够加default默认值,若是不加,数据类型的默认为0,字符串类型的默认为空串。repeated后面加[packed=true]会使用新的更高效的编码方式。

2.2 enum类型

  其意义和Java中的enum一致,在 message 中能够嵌入枚举类型

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}

  枚举类型须要注意的是,必定要有 0 值。

  枚举为 0 的是做为零值,当不赋值的时候,就会是零值。

  为了和 proto2 兼容。在 proto2 中,零值必须是第一个值。

2.3 Service接口

  果要使用 RPC(远程过程调用)系统的消息类型,能够在 .proto 文件中定义 RPC 服务接口,protocol buffer编译器将使用所选语言生成服务接口代码和stubs。因此,例如,若是你定义一个 RPC 服务,入参是 SearchRequest 返回值是 SearchResponse,你能够在你的 .proto 文件中定义它,以下所示:

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

2.4 命名规范

  message、enum、Service都采用驼峰命名法。即首字母大写开头,其中message和enum中的字段名采用下划线分隔法命名。示例以下:

message SongServerRequest {
  required string song_name = 1;
}

enum Foo {
  FIRST_VALUE = 0;
  SECOND_VALUE = 1;
}

service FooService {
  rpc GetSomething(FooRequest) returns (FooResponse);
}

2.5 特殊状况

2.5.1 为已弃用的field保存field id

  以前说过,每一个field对应惟一一个field id,可是在工做中可能会出现这么一种状况:“即在项目中某field被弃用,以后的版本再也不使用该field了,可是仍有一部分用户使用以前的开发版本,此时若彻底将对应的field id不当心应用到新的field中,必定会引发混乱”,因此此时应该将一些field id设置为保留类型,即不能被用来定义新的field,若是之后有人使用,protobuf编译器会提出出错。示例以下:

message Person {
  reserved 2, 15, 9 to 11;   #9 to 11表示九、十、11三个field id
  reserved "samples", "email";
}

注意一个reserved字段不能既有标签数字又有名字

3. Protobuf存储原理和压缩原理

3.1 存储原理

  protobuf对不一样对数据类型进行分类,分别为其选择不一样对存储格式,不过多数数据对存储格式都是键值对的key-value形式。protobuf将全部数据类型分为了一下几类:

type meaning used for
0 varint int32,int64,uint32,uint64,sint32,sint64,bool,enum
1 64-bit fixed64,sfixed64,double
2 Length-delimited string,bytes,embedded message,packed repeated fields
5 32-bit fixed32,sfixed32,float

  其中类型三、4已经在proto3中被弃用,故不列出。除了类型2的数据以外的三个类型所有是由k-v形式存储,类型2数据因为长度不定,因此在存储的时候还须要加入一个length指标,以k-l-v的形式存储。

  对于message这种结构体数据,protobuf把message中全部字段的数据表示成k-v/k-l-v形式后拼接在一块儿,减小分隔符所占用的空间。这样一来有个问题出现了,那就是如何在一长串的拼接起来的二进制数据中找到对应的field?,而且还要肯定该field是k-v存储仍是k-l-v存储的呢?

  这个问题的答案在存储field的“key”中,“key”由两个值决定,分别是field id和该field的类型编号,其计算方法是key = (field_id << 3) | type,其中|表明两个二进制类型的拼接,示例以下图所示:
avatar
  要注意的是,key在某些时地方也会被称做tag。

3.1.1 varint类型存储(type=0)

  这里须要特别说明一下的是,在type=0的数据类型中,除了正数整型外还存在负数整型,一个负数整型通常会被表示为一个很大的整数,由于计算机定义负数的符号位为数字的最高位。若是采用Varints这种编码方式表示一个负数,那么必定须要 10 个byte长度。

  然而protobuf定义了一种新的sint32/64类型,其采用ZigZag编码方式,首先将全部整数映射成无符号整数(即-1 将会被编码成 1,1 将会被编码成 2,-2 会被编码成 3,以此类推...),而后再采用Varint编码方式编码。这样一来绝对值小的整数,编码后也会有一个较小的Varint编码值,无缝对接。

3.1.2 32/64-bit类型存储(type=一、type=5)

  &type=1时,在解析时告诉解析器,该类型的数据须要一个 64 位大小的数据块便可。
  type=5时,float 和 fixed32 的 wire_type 为5,给其 32 位数据块便可。

3.1.2 Length-delimited类型存储(type=2)

  type=2时,field以k-l-v格式存储,如今假设一个message中有一个string类型的field,名字为b,以下所示:

message Test2 {
  optional string b = 2;
}

  如今咱们假设b被赋上的值为“testing”,7个英文字符,每一个字符分别对应的十六进制码为“74 65 73 74 69 6e 67”。

  以字符t为例,其二进制表示为0111 0100,将其右三位后为0000 1110,再与type=2的二进制0000 0010作“或”运算获得t字符最后的key/tag为0000 1110

  肯定了key/tag以后,接着是length,其为7——>“0000 0111”。length肯定后接着跟value,即“testing”的7个字符的二进制表示。由此一个字符串类型的protobuf的k-l-v存储就结束了。

3.2 压缩原理

  protobuf采起的压缩算法是Varints,Varint是一种紧凑的、不定长的表示数字的算法,它用一个或多个字节来表示一个数字,其中值越小的数字使用越少的字节数,这样能够节省表示较小数字的空间。

4. Protobuf的特色

4.1 优势

  操做性简单:无复杂的文档对象模型,自动生成类,语义清晰

  安全性强:没有得到.proto文件就没法破解

  兼容性强:具备向后兼容的特性,更新数据结构之后,老版本依旧能够兼容跨语言:支持C++、Python、Java

  压缩性能好:压缩后的文件体积小(因为varint压缩和option等关键字的缘由,也不像json、xml同样有<>等符号)

4.2 缺点

  可读性差:在没有.proto文件的基础下没法人为读懂传递过来的数据

  表达能力差:没法描述数据结构,没法对标记文档(html等)建模

5. 与其余序列化协议比较

   protobuf JSON XML Lua
数据结构支持 较复杂结构 简单结构 复杂结构 复杂结构
数据存储方式 二进制 文本 文本 文本
数据压缩大小 通常 通常
编码解码效率 通常 稍快
编程语言支持 C++/Python/Java 不少 不少 不少
开发难度 简单 简单 繁琐 相对繁琐
学习成本
适用范围 数据交换 数据交换 数据交换 数据保存及脚本处理
相关文章
相关标签/搜索