http://jimmee.iteye.com/blog/2042420
第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的注解。
- /**
- * 一个用于messagepack测试序列化和反序列的javabean
- *
- * @author jimmee
- */
- @MessagePackMessage
- public class Person {
- /** 编号 */
- public int id;
- /** 名字 */
- public String name;
- /**身高*/
- public double height;
- /**
- * 默认构造函数
- */
- public Person() {
- }
/**
* 一个用于messagepack测试序列化和反序列的javabean
*
* @author jimmee
*/
@MessagePackMessage
public class Person {
/** 编号 */
public int id;
/** 名字 */
public String name;
/**身高*/
public double height;
/**
* 默认构造函数
*/
public Person() {
}
序列化直接调用MessagePack的pack方法;反序列化则调用对应的unpack方法。这两个方法,都支持传递序列化和反序列化的数据类型。
1.3 与json的序列化性能对比
如下所示,通过100条数据的序列化和反序列化进行对比。
- List<Map> msgs = new ArrayList<Map>();
- for (int i = 0; i < 100; i++) {
- Map msg = new HashMap();
- msg.put(Const.FID, i);
- msg.put(Const.SUBJECT, "subject" + i);
- msg.put(Const.LABEL0, 1);
- msg.put(Const.FROM, "test@163.com");
- msg.put(Const.TO, "test@126.com");
- msg.put(Const.MODIFIED_DATE, new Date().getTime());
- msg.put(Const.RECEIVED_DATE, new Date().getTime());
- msg.put(Const.SENT_DATE, new Date().getTime());
- msgs.add(msg);
- }
List<Map> msgs = new ArrayList<Map>();
for (int i = 0; i < 100; i++) {
Map msg = new HashMap();
msg.put(Const.FID, i);
msg.put(Const.SUBJECT, "subject" + i);
msg.put(Const.LABEL0, 1);
msg.put(Const.FROM, "test@163.com");
msg.put(Const.TO, "test@126.com");
msg.put(Const.MODIFIED_DATE, new Date().getTime());
msg.put(Const.RECEIVED_DATE, new Date().getTime());
msg.put(Const.SENT_DATE, new Date().getTime());
msgs.add(msg);
}
比较结果如表1-1所示。
表1- 1 messagepack与json的性能对比
框架 |
字节大小(byte) |
序列化时间(ns) |
反序列化时间(ns) |
messagepack |
12793 |
2313335 |
529458 |
json |
17181 |
1338371 |
1776519 |
可以看出,messagepack的序列化字节数比json小将近30%;序列化时间messagepack差不多是json的两倍;反序列化时间,messagepack只需要json的30%的时间。
但是,值得注意的是,虽然messagepack的反序列化时间比较少,但是要真正转换为前端需要的类型参数格式,还需要额外的一些时间。
第2部分 protocol buffers
2.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.id = 5; Message.info = “hello”; |
则最终的 Message Buffer 中有两个 Key-Value 对,一个对应消息中的 id;另一个对应 info。
Key 用来标识具体的 field,在解包的时候,Protocol Buffer 根据 Key 就可以知道相应的 Value 应该对应于消息中的哪一个 field。
Key 的定义如下:
(field_number << 3) | wire_type |
可以看到 Key 由两部分组成。第一部分是 field_number。第二部分为 wire_type。表示 Value 的传输类型。
wire type如表2-1所示。
表2- 1 wire type说明
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 messages, packed repeated fields |
3 |
Start group |
Groups (deprecated) |
4 |
End group |
Groups (deprecated) |
5 |
32-bit |
fixed32, sfixed32, float |
在计算机内,一个负数一般会被表示为一个很大的整数,因为计算机定义负数的符号位为数字的最高位。如果采用 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
- message MessageMeta {
- required int32 id = 1;
- required string subject = 2;
- optional int32 lablel0 = 3;
- required string from = 4;
- required string to = 5;
- optional int64 modifiedDate = 6;
- optional int64 receivedDate = 7;
- optional int64 sentDate = 8;
- }
message MessageMeta {
required int32 id = 1;
required string subject = 2;
optional int32 lablel0 = 3;
required string from = 4;
required string to = 5;
optional int64 modifiedDate = 6;
optional int64 receivedDate = 7;
optional int64 sentDate = 8;
}
- message MessageMetas {
- repeated MessageMeta msg = 1;
- }
message MessageMetas {
repeated MessageMeta msg = 1;
}
2. 将message.proto文件转换为java语言的源码
例如, 执行命令:protoc -I=src --java_out=out src/messages.proto产生Messages的java文件。
3. 执行序列化和反序列化
- MessageMetas.Builder msgsBuilder = MessageMetas.newBuilder();
- for (int i = 0; i < 100; i++) {
- MessageMeta.Builder msgBuilder = MessageMeta.newBuilder();
- msgBuilder.setId(i);
- msgBuilder.setSubject("subject" + i);
- msgBuilder.setLablel0(1);
- msgBuilder.setFrom("test@163.com");
- msgBuilder.setTo("test@126.com");
- msgBuilder.setModifiedDate(new Date().getTime());
- msgBuilder.setReceivedDate(new Date().getTime());
- msgBuilder.setSentDate(new Date().getTime());
- msgsBuilder.addMsg(msgBuilder.build());
- }
- MessageMetas msgs = msgsBuilder.build();
MessageMetas.Builder msgsBuilder = MessageMetas.newBuilder();
for (int i = 0; i < 100; i++) {
MessageMeta.Builder msgBuilder = MessageMeta.newBuilder();
msgBuilder.setId(i);
msgBuilder.setSubject("subject" + i);
msgBuilder.setLablel0(1);
msgBuilder.setFrom("test@163.com");
msgBuilder.setTo("test@126.com");
msgBuilder.setModifiedDate(new Date().getTime());
msgBuilder.setReceivedDate(new Date().getTime());
msgBuilder.setSentDate(new Date().getTime());
msgsBuilder.addMsg(msgBuilder.build());
}
MessageMetas msgs = msgsBuilder.build();
之后调用相应的writeTo方法进行序列化, 调用parseFrom进行反序列化。
2.3 与json等的性能对比
表2- 2 性能对比表格
框架 |
字节大小(byte) |
序列化时间(ns) |
反序列化时间(ns) |
messagepack |
12793 |
2313335 |
529458 |
protocol buffers |
6590 |
941790 |
408571 |
json |
17181 |
1338371 |
1776519 |
可以看出,protocol buffers在字节流,序列化时间和反序列化时间方面都明显较优(即空间和时间上都比较好)。
第3部分 thrift
thrift的架构如图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 与编码无关的文本或二进制字符串
可基于基本类型定义结构体,例如:
- struct Example {
- 1:i32 number=10,
- 2:i64 bigNumber,
- 3:double decimals,
- 4:string name="thrifty"
- }
struct Example {
1:i32 number=10,
2:i64 bigNumber,
3:double decimals,
4:string name="thrifty"
}
支持的容器有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
- struct MessageMeta {
- 1:i32 id;
- 2:string subject;
- 3:i32 lablel0;
- 4:string from;
- 5:string to;
- 6:i64 modifiedDate;
- 7:i64 receivedDate;
- 8:i64 sentDate;
- }
- struct MessageMetas {
- 1:list<MessageMeta> msgs;
- }
struct MessageMeta {
1:i32 id;
2:string subject;
3:i32 lablel0;
4:string from;
5:string to;
6:i64 modifiedDate;
7:i64 receivedDate;
8:i64 sentDate;
}
struct MessageMetas {
1:list<MessageMeta> msgs;
}
2. 将定义的文件转换成相应的java源码
执行命令:thrift -gen java messages.thrift
3. 执行序列化和反序列化
- MessageMetas msgs = new MessageMetas();
- List<MessageMeta> msgList = new ArrayList<MessageMeta>();
- for (int i = 0; i < 100; i++) {
- MessageMeta msg = new MessageMeta();
- msg.setId(i);
- msg.setSubject("subject" + i);
- msg.setLablel0(1);
- msg.setFrom("test@163.com");
- msg.setTo("test@126.com");
- msg.setModifiedDate(new Date().getTime());
- msg.setReceivedDate(new Date().getTime());
- msg.setSentDate(new Date().getTime());
- msgList.add(msg);
- }
- msgs.setMsgs(msgList);
- // 序列化
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- TTransport trans = new TIOStreamTransport(out);
- TBinaryProtocol tp = new TBinaryProtocol(trans);
- msgs.write(tp);
- byte [] buf = out.toByteArray();
- // 反序列化
- ByteArrayInputStream in = new ByteArrayInputStream(buf);
- trans = new TIOStreamTransport(in);
- tp = new TBinaryProtocol(trans);
- MessageMetas msgs2 = new MessageMetas();
- msgs2.read(tp);
MessageMetas msgs = new MessageMetas();
List<MessageMeta> msgList = new ArrayList<MessageMeta>();
for (int i = 0; i < 100; i++) {
MessageMeta msg = new MessageMeta();
msg.setId(i);
msg.setSubject("subject" + i);
msg.setLablel0(1);
msg.setFrom("test@163.com");
msg.setTo("test@126.com");
msg.setModifiedDate(new Date().getTime());
msg.setReceivedDate(new Date().getTime());
msg.setSentDate(new Date().getTime());
msgList.add(msg);
}
msgs.setMsgs(msgList);
// 序列化
ByteArrayOutputStream out = new ByteArrayOutputStream();
TTransport trans = new TIOStreamTransport(out);
TBinaryProtocol tp = new TBinaryProtocol(trans);
msgs.write(tp);
byte [] buf = out.toByteArray();
// 反序列化
ByteArrayInputStream in = new ByteArrayInputStream(buf);
trans = new TIOStreamTransport(in);
tp = new TBinaryProtocol(trans);
MessageMetas msgs2 = new MessageMetas();
msgs2.read(tp);
3.3与json等的性能对比
表3- 1 性能对比
框架 |
字节大小(byte) |
序列化时间(ns) |
反序列化时间(ns) |
messagepack |
12793 |
2313335 |
529458 |
protocol buffers |
6590 |
941790 |
408571 |
thrift |
6530 |
798696 |
754458 |
json |
17181 |
1338371 |
1776519 |
通过对比,可以发现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部分 参考资料
2. http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/overview.html
3. http://jnb.ociweb.com/jnb/jnbJun2009.html
4. http://code.google.com/p/thrift-protobuf-compare/
5. http://www.tbdata.org/archives/1307
6. https://github.com/eishay/jvm-serializers/wiki/
相关推荐
MessagePack是一种高效的二进制序列化格式,它可以在C#中用于序列化和反序列化对象。与其他序列化格式相比,如JSON和XML,MessagePack的编码和解码速度更快,生成的二进制数据更小。在C#中使用MessagePack进行序列化...
MessagePack,类似于JSON的二进制序列化格式,兼具速度和效率。它在存储时对数字、多字节字符、数组等都做了很多优化,减少了无用的字符,采用二进制格式,避免了字符化带来的额外存储空间的增加。由于这些优化,...
在本篇中,我们将深入探讨如何使用DotNetty框架实现自定义协议,并结合MessagePack库进行数据序列化。 DotNetty是一个高性能、异步的网络应用程序框架,它为开发人员提供了构建TCP、UDP、HTTP、WebSocket等协议服务...
Java对象的序列化和反序列化是Java编程中一项重要的技术,主要用于将对象的状态转换为字节流,以便存储或在网络上传输。这一过程对于理解Java的IO操作、持久化数据以及实现分布式通信等场景非常关键。 首先,我们来...
本资源包含了三个流行的Java序列化框架:JBoss Marshalling、MessagePack和Protobuf-java,它们各自具有独特的优势和适用场景。 1. JBoss Marshalling: JBoss Marshalling是JBoss组织开发的一个高效、灵活的序列...
MessagePack就是一种高效、跨语言的数据序列化库,它专为速度和效率而设计,特别适合处理大数据量的场景。 **MessagePack简介** MessagePack是一种轻量级的二进制序列化格式,它的目标是比JSON更快、更小。在保持...
**MessagePack**是一种高效、轻量级的数据序列化格式,常用于网络通信和存储,尤其在跨语言数据交换中表现出色。它将数据结构转换为二进制表示,以达到比JSON更小的体积和更快的序列化与反序列化速度。本项目展示了...
MessagePack是一个基于二进制高效的对象序列化Library用于跨语言通信。它可以像JSON那样,在许多种语言之间交换结构对象;但是它比JSON更快速也更轻巧。 支持Python、Ruby、Java、C/C++、Javascript等众多语言。 比...
- 自定义类型序列化:通过实现`MessagePack.PackerFactory`和`MessagePack.UnpackerFactory`接口,可以自定义特定类型的序列化和反序列化逻辑。 - 类型安全:MsgPack提供了一种方式来确保反序列化的对象类型与原始...
5. **Apache Thrift**:另一种跨语言的序列化框架,它不仅提供序列化,还提供了服务接口定义和RPC框架。 6. **MessagePack**:轻量级的二进制序列化格式,比JSON更快更小,适合嵌入式设备和实时系统。 7. **设计...
MessagePack是一种高效的二进制序列化格式,常用于网络通信和数据存储,尤其在Java社区中广泛应用。...同时,这也是一次学习数据结构和序列化技术的好机会,特别是对于那些涉及网络通信和分布式系统开发的项目。
序列化和反序列化是计算机科学中的重要概念,特别是在数据传输、持久化存储以及跨进程通信等场景下。本文将深入探讨这两个概念及其在实际应用中的相关知识。 首先,我们来理解什么是序列化。序列化是指将对象的状态...
在 Rust 生态中,还有其他特定领域的序列化库,如 rust-protobuf 和 rust-protobuf-json,它们专门处理 Google Protocol Buffers(protobuf)的序列化和反序列化。protobuf 是一种高效的跨语言数据交换格式,广泛...
NServiceBus.MessagePack 通过添加对消息序列化的支持 社区支持 预期所有开发人员都将或拥有以使用NServiceBusExtensions。 赞助商 通过 支持这个项目。 公司头像将在此处显示,并带有网站链接。 该头像还将被添加...
MessagePack(Msgpack) 是一种紧凑、快速、二进制序列化格式,允许你在多种语言间交换数据。它类似于 JSON,但提供了更高的效率和更小的尺寸。尽管是一种二进制格式,但 MessagePack 设计之初就考虑到了跨语言使用...
MessagePack是一种高效的二进制序列化格式,常用于网络通信和数据存储,特别是在高性能和低带宽需求的场景中。它能够将复杂的数据结构转换为紧凑的二进制表示,从而减少传输的数据量。在Java开发中,MessagePack库...
MessagePack是一种高效的二进制序列化格式,常用于网络通信和数据存储,特别是在游戏开发和实时系统中。它能够将JSON、XML等文本格式的数据转换为更紧凑的二进制形式,减小数据传输和存储的体积。在Unity游戏引擎中...
cpp-cmpMessagePack序列化是C++中的一种实现,它基于MessagePack协议,这是一种高效、轻量级的数据序列化格式,特别适用于网络通信和数据存储。MessagePack的目标是比JSON更紧凑,但又能保持足够的易读性。 ...
Java 的 MessagePack 序列化器实现 / msgpack.org[Java]用于 Java 的 MessagePackMessagePack是一种二进制序列化格式。如果您需要一种快速而紧凑的 JSON 替代方案,MessagePack 就是您的好帮手。例如,小整数可以用...