第1部分 messagepack说明1.1messagepack的消息编码说明为什么messagepack比json序列化使用的字节流更少, 可通过图1-1、图1-2有个直观的感觉。 图1- 1 messagepack与json的格式对比1
图1- 2 messagepack与json的格式对比2 messagepack的具体的消息格式如图1-3所示,messagepack的数据类型主要分类两类:固定长度类型和可变长度类型。
图1- 3 messagepack的消息格式 messagepack的具体类型信息表示如图1-4所示。 图1- 4 messagepack的类型信息 1.2 messagepack的序列化和反序列化方式现在msgpack能支持基本的数据类型,支持list和map, 还支持自定义的数据类型。例子1, 序列化和反序列化一个javabean, 只要加上@MessagePackMessage的注解。
Java代码
序列化直接调用MessagePack的pack方法;反序列化则调用对应的unpack方法。这两个方法,都支持传递序列化和反序列化的数据类型。 1.3 与json的序列化性能对比如下所示,通过100条数据的序列化和反序列化进行对比。
Java代码
比较结果如表1-1所示。 表1- 1 messagepack与json的性能对比
可以看出,messagepack的序列化字节数比json小将近30%;序列化时间messagepack差不多是json的两倍;反序列化时间,messagepack只需要json的30%的时间。 但是,值得注意的是,虽然messagepack的反序列化时间比较少,但是要真正转换为前端需要的类型参数格式,还需要额外的一些时间。 第2部分 protocol buffers2.1 protocol buffers的消息编码说明Protocol Buffers支持的数据类型如下图所示:
图2- 1 protocol buffers支持的数据类型。 首先对Varint进行说明。Varint 是一种紧凑的表示数字的方法。它用一个或多个字节来示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。 比如对于 int32 类型的数字,一般需要 4 个 byte 来表示。但是采用 Varint,对于很小的 int32 类型的数字,则可以用 1 个 byte 来表示。当然,采用 Varint 表示法,大的数字则需要 5 个 byte 来表示。从统计的角度来说,一般不会所有的消息中的数字都是大数,因此大多数情况下,采用 Varint 后,可以用更少的字节数来表示数字信息。 Varint 中的每个 byte 的最高位 bit 有特殊的含义,如果该位为 1,表示后续的 byte 也是该数字的一部分,如果该位为 0,则结束。其他的 7 个 bit 都用来表示数字。因此小于 128 的数字都可以用一个 byte 表示。大于 128 的数字,比如 300,会用两个字节来表示:1010 1100 0000 0010。 图2-2说明了 Google Protocol Buffer 如何解析两个 bytes。注意到最终计算前将两个 byte 的位置相互交换过一次,这是因为 Google Protocol Buffer 字节序采用 little-endian 的方式。
图2- 2 protocol buffers解析两个字节 消息经过序列化后会成为一个二进制数据流,该流中的数据为一系列的 Key-Value 对,如图2-3所示。
图2- 3 protocol buffers的消息流 采用这种 Key-Pair 结构无需使用分隔符来分割不同的 Field。对于可选的 Field,如果消息中不存在该 field,那么在最终的 Message Buffer 中就没有该 field,这些特性都有助于节约消息本身的大小。 假设我们生成如下的一个消息Message:
则最终的 Message Buffer 中有两个 Key-Value 对,一个对应消息中的 id;另一个对应 info。 Key 用来标识具体的 field,在解包的时候,Protocol Buffer 根据 Key 就可以知道相应的 Value 应该对应于消息中的哪一个 field。 Key 的定义如下:
可以看到 Key 由两部分组成。第一部分是 field_number。第二部分为 wire_type。表示 Value 的传输类型。 wire type如表2-1所示。 表2- 1 wire type说明
在计算机内,一个负数一般会被表示为一个很大的整数,因为计算机定义负数的符号位为数字的最高位。如果采用 Varint 表示一个负数,那么一定需要 5 个 byte。为此 Google Protocol Buffer 定义了 sint32,sint64 类型,采用 zigzag 编码。 Zigzag 编码用无符号数来表示有符号数字,正数和负数交错,如图2-3所示。使用 zigzag 编码,绝对值小的数字,无论正负都可以采用较少的 byte 来表示,充分利用了 Varint 这种技术。
图2- 4 ZigZag编码 2.2 protocol buffers的序列化和反序列化步骤: 创建消息的定义文件.proto; 使用protoc工具将proto文件转换为相应语言的源码; 使用类库支持的序列化和反序列化方法进行操作。
以同样的数据的操作为例: 1. 定义proto文件messages.ptoto
Java代码
Java代码
2. 将message.proto文件转换为java语言的源码 例如, 执行命令:protoc -I=src --java_out=out src/messages.proto产生Messages的java文件。 3. 执行序列化和反序列化
Java代码
之后调用相应的writeTo方法进行序列化, 调用parseFrom进行反序列化。 2.3 与json等的性能对比表2- 2 性能对比表格
可以看出,protocol buffers在字节流,序列化时间和反序列化时间方面都明显较优(即空间和时间上都比较好)。 第3部分 thriftthrift的架构如图3-1所示。图3-1显示了创建server和client的stack。最上面的是IDL,然后生成Client和Processor。红色的是发送的数据。protocol和transport 是Thrift运行库的一部分。通过Thrift 你只需要关心服务的定义,而不需要关心protocol和transport。 Thrift支持 text 和 binary protocols,binary protocols要比text protocols,但是有时候 text protocols比较有用(例如:调试的时候)。支持的协议有: TBinaryProtocol :直接的二进制格式 TCompactProtocol :效率和高压缩编码数据 TDenseProtocoal : 和TCompactProtocol相似,但是省略了meta信息,从哪里发送的,增加了receiver。还在实验中,java实现还不可用。 TJSONProtocoal:使用JSON TSImpleJSONProtocoal :只写的protocol使用JSON。适合被脚本语言转化 TDebugProtocoal:使用人类可读的text 格式 帮助调试
图3- 1 thrift架构图 上面的protocol 说明了传送的是什么样的数据,Thrift 的transports 则说明了怎样传送这些数据。支持的transport: TSocket :使用 blocking socket I/O; TFramedTransport :以帧的形式发送,每帧前面是一个长度。要求服务器来non-blocking server; TFileTransport :写到文件; TMemoryTransport :使用内存 I/O ,java实现中在内部使用了ByteArrayOutputStream; TZlibTransport 压缩 使用zlib,在java实现中还不可用。 最后,thrift 提供了servers: TSimpleServer :单线程server,使用标准的blocking IO,用于测试; TThreadPoolServer:多线程server ,使用标准的blocking IO; TNonblockingServer 多线程 server,使用 non-blocking IO (java实现中使用了NIO channels),TFramedTransport必须使用在这个服务器。 一个server只允许定义一个接口服务。这样的话多个接口需要多个server。这样会带来资源的浪费。通常可以通过定义一个组合服务来解决。 3.1 thrift的消息编码说明1. 支持的数据类型 所有编程语言中都可用的关键类型。 bool 布尔值,真或假 byte 有符号字节 i16 16位有符号整数 i32 32位有符号整数 i64 64位有符号整数 double 64位浮点数 string 与编码无关的文本或二进制字符串 可基于基本类型定义结构体,例如:
Java代码
支持的容器有list<type>,set<type>和Map<type1,type2>。 若使用TCompactProtocol,传递的消息形式如图3-2所示: 图3- 2 thrift的compact方式的消息流 在这种方式下,对整数而言,也是采用可变长度的方式进行实现。一个字节,最高位表示是否还有数据,低7位是实际的数据,如图3-3所示, 整数106903的编码, 相比普通的int类型,节省一个字节。
图3- 3 compact方式对一个整数106903进行编码 3.2thrift的序列化和反序列化方式步骤: 创建thrift接口定义文件; 将thrift的定义文件转换为对应语言的源代码; 选择相应的protocol,进行序列化和反序列化。 仍以同样的数据对象为例子: 定义thrift文件messages.thrift
Java代码
2. 将定义的文件转换成相应的java源码 执行命令:thrift -gen java messages.thrift 3. 执行序列化和反序列化
Java代码
3.3与json等的性能对比表3- 1 性能对比
通过对比,可以发现thrift总的来说,都比较不错。 第4部分 小结通过对messagepack,protocol buffers以及thrift的分析,主要分析了这些框架的序列化和反序列化部分的内容。实际上messagepack和thrift都还有自己的rpc调用框架。 所有的测试都是在本机上进行,基于100条元数据进行测试。可能不同数据,以及不同的规模,测试结果应该会存在差别,https://github.com/eishay/jvm-serializers/wiki/的有比较好的测试结果说明。根据自己的测试,从性能上说,messagepack,protocol buffers以及thrift都比json好(在测试时,发现messagepack序列化的时间稍微多一些)。 从编程语言上来说,messagepack,protocol buffers以及thrift,当然还包括json,都是支持跨语言的通讯的。 从接口定义的灵活性来(或者是否支持动态类型),messagepack较protocol buffers以及thrift较好,后两者都要预先定义schema并相对固定。
实际工作中, 一般都采用protocol buffers或者thrift.
第5部分 参考资料1. http:/// 2. http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/overview.html 3. http://jnb./jnb/jnbJun2009.html 4. http://code.google.com/p/thrift-protobuf-compare/ 6. https://github.com/eishay/jvm-serializers/wiki/ 8. http://pypi./pypi/msgpack-python/
|
|
来自: 怎么了啊早上 > 《Thrift&Protobuf》