Protobuf的优势在哪里?

什么是Protobuf

Protobuf是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它的设计非常适用于在网络通讯中的数据载体,很适合做数据存储或 RPC 数据交换格式,它序列化出来的数据量少再加上以K-V的方式来存储数据,对消息的版本兼容性非常强,可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。

Protobuf 消息

message由至少一个字段组合而成,类似于C语言中的结构。每个字段都有一定的格式:

限定修饰符 | 数据类型 | 字段名称 | = | 字段编码值 | [字段默认值]

限定修饰符

  • message就是相当于class
  • Required:表示是一个必须字段,必须相对于发送方,在发送消息之前必须设置该字段的值;对于接收方,必须能够识别该字段的意思。发送之前没有设置required字段或者无法识别required字段都会引发编解码异常,导致消息被丢弃。
  • Optional:表示是一个可选字段,可选对于发送方,在发送消息时,可以有选择性的设置或者不设置该字段的值;对于接收方,如果能够识别可选字段就进行相应的处理,如果无法识别,则忽略该字段,消息中的其它字段正常处理。很多接口在升级版本中都把后来添加的字段都统一的设置为optional字段,这样老的版本无需升级程序也可以正常的与新的软件进行通信,只不过新的字段无法识别而已,因为并不是每个节点都需要新的功能,因此可以做到按需升级和平滑过渡。
  • Repeated:表示该字段可以包含0 ~ N个元素,可以看作是在传递数组。

Protobuf的优势

  • (1)序列化后体积相比Json和XML很小,适合网络传输
  • (2)支持跨平台多语言
  • (3)消息格式升级和兼容性不错,“向后”兼容性好
  • (4)序列化反序列化速度很快,快于Json的处理速速
  • (5)Protobuf 语义更清晰,无需类似 XML 解析器的东西(因为 Protobuf 编译器会将 .proto 文件编译生成对应的数据访问类以对 Protobuf 数据进行序列化、反序列化操作)。

Protobuf与JSON的数据大小对比:

(1)Protobuf的数据大小

(2)JSON的数据大小

为什么Protobuf的速度快

Protobuf 的主要优点在于性能高。它以高效的二进制方式存储,比 XML 小 3 到 10 倍,快 20 到 100 倍。
对于这些 “小 3 到 10 倍”,“快 20 到 100 倍”的说法,严肃的程序员需要一个解释。因此在本文的最后,让我们稍微深入 Protobuf 的内部实现吧。

有两项技术保证了采用 Protobuf 的程序能获得相对于 XML 极大的性能提高。

Zigzag 编码

第一点,我们可以考察 Protobuf 序列化后的信息内容。您可以看到 Protocol Buffer 信息的表示非常紧凑,这意味着消息的体积减少,自然需要更少的资源。比如网络上传输的字节数更少,需要的 IO 更少等,从而提高性能。这个利益于Protobuf 采用的非常巧妙的 Encoding 方法:Zigzag 编码。

介绍Zigzag编码前,先普及一下一个叫做 Varint 的术语。

Varint 是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。比如对于 int32 类型的数字,一般需要 4 个 byte 来表示。但是采用 Varint,对于很小的 int32 类型的数字,则可以用 1 个 byte 来表示。当然凡事都有好的也有不好的一面,采用 Varint 表示法,大的数字则需要 5 个 byte 来表示。从统计的角度来说,一般不会所有的消息中的数字都是大数,因此大多数情况下,采用 Varint 后,可以用更少的字节数来表示数字信息。

消息经过序列化后会成为一个二进制数据流,该流中的数据为一系列的 Key-Value 对。下面就详细介绍一下Varint。

Varint 中的每个 byte 的最高位 bit 有特殊的含义,如果该位为 1,表示后续的 byte 也是该数字的一部分,如果该位为 0,则结束。其他的 7 个 bit 都用来表示数字。因此小于 128 的数字都可以用一个 byte 表示。大于 128 的数字,比如 300,会用两个字节来表示:1010 1100 0000 0010。

在计算机内,一个负数一般会被表示为一个很大的整数,因为计算机定义负数的符号位为数字的最高位。如果采用 Varint 表示一个负数,那么一定需要 5 个 byte。为此 Google Protocol Buffer 定义了 sint32 这种类型,采用 zigzag 编码,用无符号数来表示有符号数字,正数和负数交错,-1将会被编码成1,1将会被编码成2,-2会被编码成3。

使用 zigzag 编码,绝对值小的数字,无论正负都可以采用较少的 byte 来表示,充分利用了 Varint 这种技术。其他的数据类型,比如字符串等则采用类似数据库中的 varchar 的表示方法,即用一个 varint 表示长度,然后将其余部分紧跟在这个长度部分之后即可。

比如我们定义如下protobuf文件:

package lm;

message helloworld
 {
    required int32     id = 1;  // ID
    required string    str = 2;  // str
    optional int32     opt = 3;  //optional field
 }

转化为相应的.java文件后,我们可以发现有这个方法,进行二进制的转存。

public void writeTo(com.google.protobuf.CodedOutputStream output)
                        throws java.io.IOException {
      getSerializedSize();
      if (((bitField0_ & 0x00000001) == 0x00000001)) {
        output.writeUInt32(1, width_);
      }
      if (((bitField0_ & 0x00000002) == 0x00000002)) {
        output.writeUInt32(2, height_);
      }
      getUnknownFields().writeTo(output);
    }

用 Protobuf 序列化后的字节序列为:

08 65 12 06 48 65 6C 6C 6F 77

而如果用 XML,则类似这样:

31 30 31 3C 2F 69 64 3E 3C 6E 61 6D 65 3E 68 65

6C 6C 6F 3C 2F 6E 61 6D 65 3E 3C 2F 68 65 6C 6C

6F 77 6F 72 6C 64 3E

Protobuf解包

先来了解一下 XML 的封解包过程。XML 需要从文件中读取出字符串,再转换为 XML 文档对象结构模型。之后,再从 XML 文档对象结构模型中读取指定节点的字符串,最后再将这个字符串转换成指定类型的变量。这个过程非常复杂,其中将 XML 文件转换为文档对象结构模型的过程通常需要完成词法文法分析等大量消耗 CPU 的复杂计算。

Protobuf解包,只需要简单地将一个二进制序列,按照指定的格式读取到对应的结构类型中就可以了。消息的 decoding 过程也可以通过几个位移操作组成的表达式计算即可完成,速度非常快。

Protobuf压缩优势主要在于对integer的压缩,甚至有符号无符号都采用了不同的编码方式,能够提供极高的压缩比。而字符串对象目前据我所知是没有编码的,不过可以对pb再次进行压缩;

部分内肉转自:

文章目录
  1. 1. 什么是Protobuf
    1. 1.1. Protobuf 消息
    2. 1.2. 限定修饰符
  2. 2. Protobuf的优势
  3. 3. 为什么Protobuf的速度快
    1. 3.1. Zigzag 编码
    2. 3.2. Protobuf解包
|