【协议】MessagePack, Protocol Buffers和Thrift序列化框架原理和比较说明

分类: 知识积累 |
1.1messagepack的消息编码说明
为什么messagepack比json序列化使用的字节流更少,
http://dl2.iteye.com/upload/attachment/0095/8875/ab7fc234-7169-3968-bdd8-456811d72dea.png
图1-
http://dl2.iteye.com/upload/attachment/0095/8877/df92b4f8-e243-3e6d-9de4-af8475b34288.png
图1-
messagepack的具体的消息格式如图1-3所示,messagepack的数据类型主要分类两类:固定长度类型和可变长度类型。
http://dl2.iteye.com/upload/attachment/0095/8879/44f0c34f-45cc-3ed4-ac53-10059b76fc77.png
图1-
messagepack的具体类型信息表示如图1-4所示。
http://dl2.iteye.com/upload/attachment/0095/8881/b35ad7cf-8b67-3c7a-bd78-43c95f799b53.png
图1-
1.2 messagepack的序列化和反序列化方式
现在msgpack能支持基本的数据类型,支持list和map,
-
- @MessagePackMessage
- public
class Person { -
- public
int id; -
- public
String name; -
- public
double height; -
- public
Person() { - }
序列化直接调用MessagePack的pack方法;反序列化则调用对应的unpack方法。这两个方法,都支持传递序列化和反序列化的数据类型。
1.3 与json的序列化性能对比
如下所示,通过100条数据的序列化和反序列化进行对比。
- List
- for
( inti 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-
框架 |
字节大小(byte) |
序列化时间(ns) |
反序列化时间(ns) |
messagepack |
12793 |
2313335 |
529458 |
json |
17181 |
|
1776519 |
可以看出,messagepack的序列化字节数比json小将近30%;序列化时间messagepack差不多是json的两倍;反序列化时间,messagepack只需要json的30%的时间。
但是,值得注意的是,虽然messagepack的反序列化时间比较少,但是要真正转换为前端需要的类型参数格式,还需要额外的一些时间。
第2部分 protocol buffers
2.1 protocol buffers的消息编码说明
Protocol
http://dl2.iteye.com/upload/attachment/0095/8885/ca414f3b-69dd-3df3-99e1-445a75f8d443.png
图2-
首先对Varint进行说明。Varint
比如对于
Varint
图2-2说明了
http://dl2.iteye.com/upload/attachment/0095/8891/0c2cdf3b-5c6d-368b-b254-9f1f80268a6e.pngProtocol
图2-
消息经过序列化后会成为一个二进制数据流,该流中的数据为一系列的
http://dl2.iteye.com/upload/attachment/0095/8893/16a8a820-3d41-3e0c-9701-6557ff7aba07.pngProtocol
图2-
采用这种
假设我们生成如下的一个消息Message:
|
则最终的
Key
Key
|
可以看到
wire
表2-
Type |
Meaning |
Used |
0 |
Varint |
int32, |
1 |
64-bit |
fixed64, |
2 |
Length-delimited |
string, |
3 |
Start |
Groups |
4 |
End |
Groups |
5 |
32-bit |
fixed32, |
在计算机内,一个负数一般会被表示为一个很大的整数,因为计算机定义负数的符号位为数字的最高位。如果采用
Zigzag
http://dl2.iteye.com/upload/attachment/0095/8897/a9df0031-174d-34cf-949a-a505472ae6c9.pngProtocol
图2-
2.2 protocol buffers的序列化和反序列化
步骤:
创建消息的定义文件.proto;
使用protoc工具将proto文件转换为相应语言的源码;
使用类库支持的序列化和反序列化方法进行操作。
以同样的数据的操作为例:
1.
-
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; - }
2.
例如,
3.
-
MessageMetas.Builder
msgsBuilder = MessageMetas.newBuilder(); - for
( inti 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方法进行序列化,
2.3 与json等的性能对比
表2-
框架 |
字节大小(byte) |
序列化时间(ns) |
反序列化时间(ns) |
messagepack |
12793 |
2313335 |
529458 |
protocol |
6590 |
941790 |
408571 |
json |
17181 |
|
1776519 |
可以看出,protocol
第3部分 thrift
thrift的架构如图3-1所示。图3-1显示了创建server和client的stack。最上面的是IDL,然后生成Client和Processor。红色的是发送的数据。protocol和transport
Thrift支持
TBinaryProtocol
TCompactProtocol
TDenseProtocoal
TJSONProtocoal:使用JSON
TSImpleJSONProtocoal
TDebugProtocoal:使用人类可读的text
http://dl2.iteye.com/upload/attachment/0095/8899/4f553f0d-3572-3a89-9886-acf906613b21.pngProtocol
图3-
上面的protocol
TSocket
TFramedTransport
TFileTransport
TMemoryTransport
TZlibTransport
最后,thrift
TSimpleServer
TThreadPoolServer:多线程server
TNonblockingServer
一个server只允许定义一个接口服务。这样的话多个接口需要多个server。这样会带来资源的浪费。通常可以通过定义一个组合服务来解决。
3.1 thrift的消息编码说明
1.
所有编程语言中都可用的关键类型。
bool
byte
i16
i32
i64
double
string
可基于基本类型定义结构体,例如:
-
struct
Example { - 1:i32
number= 10, - 2:i64
bigNumber, - 3:double
decimals, - 4:string
name= "thrifty" - }
支持的容器有list,set和Map。
若使用TCompactProtocol,传递的消息形式如图3-2所示:
http://dl2.iteye.com/upload/attachment/0095/8901/72bcb56e-5e94-39e1-ba6f-3bb7eae82950.pngProtocol
图3-
在这种方式下,对整数而言,也是采用可变长度的方式进行实现。一个字节,最高位表示是否还有数据,低7位是实际的数据,如图3-3所示,
http://dl2.iteye.com/upload/attachment/0095/8903/3b198e67-86b5-395a-b75a-e26df47456c3.pngProtocol
图3-
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
msgs; - }
2.
执行命令:thrift
3.
-
MessageMetas
msgs new= MessageMetas(); -
List
msgList = new ArrayList(); - for
( inti 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-
框架 |
字节大小(byte) |
序列化时间(ns) |
反序列化时间(ns) |
messagepack |
12793 |
2313335 |
529458 |
protocol |
6590 |
941790 |
408571 |
thrift |
6530 |
798696 |
754458 |
json |
17181 |
|
1776519 |
通过对比,可以发现thrift总的来说,都比较不错。
第4部分 小结
通过对messagepack,protocol
所有的测试都是在本机上进行,基于100条元数据进行测试。可能不同数据,以及不同的规模,测试结果应该会存在差别,https://github.com/eishay/jvm-serializers/wiki/的有比较好的测试结果说明。根据自己的测试,从性能上说,messagepack,protocol
从编程语言上来说,messagepack,protocol
从接口定义的灵活性来(或者是否支持动态类型),messagepack较protocol
第5部分 参考资料
2.
3.
4.
5.
6.