文章目录

一、前言二、protobuf优点1.压缩率高2.解析快小插曲:如果传输数据内容和tag内容相同,那么不会导致解析问题吗?Varint编码Zigzag编码TLV-Tag-Length-Value

总结扩展—Thirft

一、前言

protobuf全称protocol buffers,是一种语言无关、平台无关、可扩展的序列化结构数据方法。

在用途上,与JSON/XML类似。

二、protobuf优点

压缩率高解析快多语言支持

1.压缩率高

protobuf基于接口描述语言IDL(Interface Description Language)实现消息结构的定义,传输数据的两端都需要定义该消息结构,并保存在.proto文件中,这样就不需要在消息数据中定义结构信息,自然就把空间压榨到极限了。

package my;

message helloworld

{

required int32 id = 1;

required string str = 2;

optional int32 wow = 3;

}

除此之外,每个消息项前面都会有对应的tag,才能解析对应的数据类型,类似于计算机网络中传输IP数据包也需要分隔符来标识一样。

对于protobuf,tag的大小是一个字节,即八位,tag的计算方式: tag = (field_number << 3) | wire_type,

其中,上面定义的1,2,3可以类比json中的key。field_number是.proto文件用于定义某个字段,比如对于上述消息结构,id是1,str是2,wow是3,

wire_type是google官方定义的,它是消息结构类型的一种再次分类,每个wire_type都可以对应多种数据类型,每种数据类型都有对应的wire_type:

可以观察到,protobuf支持的wire_type 范围是0~5,对应二进制也就是000~101,正好是三位,那么按照tag计算公式,field_number左移三位之后,再或上wire_type就组成了tag。这样就总共是六位,放在一个字节中,表示tag,就可以标识该字段的结构信息。

因此在判断wire_type类型的时候,只需要取后三位。

2.解析快

我们先不说为什么解析快,我们先看一个小插曲。

小插曲:如果传输数据内容和tag内容相同,那么不会导致解析问题吗?

protobuf是通过Varint编码来解决这个问题的。

Varint编码

在说varint之前,我们回顾一下,传输int需要四字节,但如果这个数用不到四字节,那么会导致浪费,例如对于整数267,二进制表示是00000000 00000000 00000001 00001011,前两个字节就是浪费的。

varint是一种特殊的编码,例如下图是两个字节(这两个字节其实对于varint编码来说,表示267,why?我们后面就见分晓):

第一个字节最高是1,表示下一个字节也是其想表述的数据的组成部分。反之,0则表示下一个字节与当前字节没有关系。

这样的话,其实上面16位里,只有14位是有实际数据意义的,从左到右先放高位,那么就是0000010 0001011,连一起就是00000100001011,正好就是前面我们的例子267的二进制表示

那么,varint编码有什么问题吗?

如果想表示-1,二进制是11111111 11111111 11111111 11111111 ,用varint编码效率很低。

Zigzag编码

Zigzag编码规则如下:

如果数据是负数,那么套用2*|x|-1来编码表示如果数据是正数,那么套用2*|x| 来编码表示

那么对于-1,就编成1,再二进制表示,就是00000001

上面的编码都是基于数字编码,那么如果传输字符串,就显得不太方便。

TLV-Tag-Length-Value

这不是一种编码格式,而是一种传输规则,对于传输字符串,Tag还是起到分隔符的作用,Length表示字符串的长度,Value表示字符的具体值,不进行编码。

总结

相比JSON和XML格式,Protobuf拥有更好的压缩度和解析速度,但可能就不具有JSON和XML那样的可读性。

扩展—Thirft

thrift IDL文件 thrift IDL不支持无符号的数据类型,因为很多编程语言中不存在无符号类型,thrift支持一下几种基本的数据类型

byte: 有符号字节

i16: 16位有符号整数

i32: 32位有符号整数

i64: 63位有符号整数

double: 64位浮点数

string: 字符串

此外thrift还支持以下容器类型:

list: 一系列由T类型的数据组成的有序列表,元素可以重复;

set: 一系列由T类型的数据组成的无序集合,元素不可重复;

map: 一个字典结构,Key为K类型,Value为V类型,相当于java中的HashMap;

thrift容器中元素的类型可以是除了service之外的任何类型,包括exception

thirft支持struct类型,目的就是讲一些数据聚合在一起,方便传输管理,struct定义形式如下:

struct People {

1:string name;

2:i32 age;

3:string gender;

}

thrift支持枚举类型,定义形式如下:

enum Gender {

MALE,

FEMALE

}

thrift支持自定义异常类型exception,异常定义形式如下:

exception RequestException {

1:i32 code;

2:string reason;

}

thrift定义服务相当于Java中创建接口一样,创建的service经过代码生thrift代码生成工具编译后就会生成客户端和服务端的框架代码,service的定义形式如下:

service HelloWorldService {

// service中可以定义若干个服务,相当于Java Interface中定义的方法

string doAction(1:string name, 2:i32 age);

}

thrift支持给类型定义别名,如下所示:

typedef i32 int

typedef i64 long

thrift也支持常量的定义,使用const关键字:

const i32 MAX_RETRIES_TIME = 10;

const string MY_WEBSITE = "http://facebook.com";

thrift支持命名空间,命名空间相当于Java中的package,主要用于组织代码,thrift使用关键字namespace定义命名空间,格式是namespace 语言名 路径,如下示例所示:

namespace java com.test.thrift.demo

thrift也支持文件包含,相当于CPP中的include,Java中的import,使用关键字include:

include "global.thrift"

#、//、/**/都可以作为thrift文件中的注释。

thrift提供两个关键字required和optional,分别用于表示对应的字段是必填的还是可选的(推荐尽量使用optional),如下所示:

struct People {

1:required string name;

2:optional i32 age;

}