Hessian、Msgpack 和 JSON 实例对比
前段时间,翻译了 Hessian 2.0 的序列化协议,发布在了 Hessian 2.0 序列化协议(中文版)。但是,其中有很多言语不详之处。所以,接下来会用几篇文章来详细解释并实践一下 Hessian 序列化协议,以求做到知其然知其所以然。
目录如下:
Hessian 2.0 序列化协议(中文版) — Hessian 序列化协议的中文翻译版。根据后面的“协议解释与实战”系列文章,增加了协议内容错误提示。
Hessian 协议解释与实战(一):布尔、日期、浮点数与整数 — 介绍布尔型数据、日期类型、浮点类型数据和整数类型数据等四种类型的数据的处理。
Hessian 协议解释与实战(二):长整型、二进制数据与 Null — 介绍长整数类型数据、二进制数据和
null
等三种类型的数据的处理。Hessian 协议解释与实战(三):字符串 — 专门介绍了关于字符串的处理。由于字符串需要铺垫的基础知识比较多,处理细节也有繁琐,所以单独成篇来介绍。
Hessian 源码分析(Java) — 开始第四篇分析之前,先来介绍一下 Hessian 的源码实现。方便后续展开说明。
Hessian 协议解释与实战(四):数组与集合 — 铺垫了一些关于实例对象的处理,重点介绍关于数组和集合的相关处理。
Hessian 协议解释与实战(五):对象与映射 — 重点介绍关于对象与映射的相关处理。
Hessian、Msgpack 和 JSON 实例对比 — 用实例对比 JSON、Hessian 和 MessagePack 的区别。
Avro、ProtoBuf、Thrift 的模式演进之路 — 翻译的 Martin Kleppmann 的文章,重点对比了 Avro、ProtoBuf、Thrift 的序列化处理思路。
本文用实际来对比一下 JSON、Hessian 和 MessagePack 的区别。
模型
package com.diguage;
import java.math.BigDecimal;
import java.util.Date;
/**
* 用户
*
* @author D瓜哥 · https://www.diguage.com
*/
public class User {
private Integer id;
private String name;
private Date birthday;
private BigDecimal money;
public User() {
}
public User(Integer id, String name, Date birthday, BigDecimal money) {
this.id = id;
this.name = name;
this.birthday = birthday;
this.money = money;
}
// 各种 Setter 和 Getter 方法
}
序列化单个实例
BigDecimal money = new BigDecimal("1234.56789")
.setScale(2, BigDecimal.ROUND_HALF_UP);
int id = 4;
String name = "diguage";
Date date = new Date();
User user = new User(id, name, date, money);
// 序列化 user
JSON
{
"id": 4,
"name": "diguage",
"birthday": "2022-08-05 11:24:21",
"money": 1234.57
}
MessagePack
== Object: com.diguage.User ==
== object: json length=74(下面是格式化代码) ==
{ (1)
"id": 4,
"name": "diguage",
"birthday": "2022-08-05 11:24:21",
"money": 1234.57
}
== object: msgpack result ==
.... 0 ~ 27 ....
-108 0x94 10010100 (2)
4 0x04 00000100 (3)
-89 0xA7 10100111 (4)
100 0x64 01100100 d
105 0x69 01101001 i
103 0x67 01100111 g
117 0x75 01110101 u
97 0x61 01100001 a
103 0x67 01100111 g
101 0x65 01100101 e
-49 0xCF 11001111 (5)
0 0x00 00000000
0 0x00 00000000
1 0x01 00000001
-126 0x82 10000010
108 0x6C 01101100 l
19 0x13 00010011
89 0x59 01011001 Y
-112 0x90 10010000
-89 0xA7 10100111 (6)
49 0x31 00110001 1
50 0x32 00110010 2
51 0x33 00110011 3
52 0x34 00110100 4
46 0x2E 00101110 .
53 0x35 00110101 5
55 0x37 00110111 7
1 | 数据 |
2 | 表示一个对象的开始,同时也表示有四个字段; |
3 | 第一个字段 id 的值 4 ; |
4 | 第二个字段 name 类型及字段长度 7,MsgPack 直接使用 UTF-8 对字符串进行编码;关于字符串编码,请看: 细说编码与字符集。 |
5 | 第三个字段 birthday 类型,日期类型,后面紧跟八个字节表示精确到毫秒的时间戳; |
6 | 第四个字段 money 类型及字段长度。在 MsgPack 原生的 Jar 包,提供了 BigDecimalTemplate 来处理 BigDecimal 数据,它将 BigDecimal 处理成字符串。所以,这里的类型和第二个字段 name 的类型是一样的。 |
Hessian
== Object: com.diguage.User ==
== object: json length=74(下面是格式化代码) ==
{ (1)
"id": 4,
"name": "diguage",
"birthday": "2022-08-05 11:24:21",
"money": 1234.57
}
== object: hessian result ==
.... 0 ~ 99 ....
67 0x43 01000011 C (2)
16 0x10 00010000 (3)
99 0x63 01100011 c
111 0x6F 01101111 o
109 0x6D 01101101 m
46 0x2E 00101110 .
100 0x64 01100100 d
105 0x69 01101001 i
103 0x67 01100111 g
117 0x75 01110101 u
97 0x61 01100001 a
103 0x67 01100111 g
101 0x65 01100101 e
46 0x2E 00101110 .
85 0x55 01010101 U
115 0x73 01110011 s
101 0x65 01100101 e
114 0x72 01110010 r
-108 0x94 10010100 (4)
2 0x02 00000010 (5)
105 0x69 01101001 i
100 0x64 01100100 d
4 0x04 00000100 (6)
110 0x6E 01101110 n
97 0x61 01100001 a
109 0x6D 01101101 m
101 0x65 01100101 e
8 0x08 00001000 (7)
98 0x62 01100010 b
105 0x69 01101001 i
114 0x72 01110010 r
116 0x74 01110100 t
104 0x68 01101000 h
100 0x64 01100100 d
97 0x61 01100001 a
121 0x79 01111001 y
5 0x05 00000101 (8)
109 0x6D 01101101 m
111 0x6F 01101111 o
110 0x6E 01101110 n
101 0x65 01100101 e
121 0x79 01111001 y
96 0x60 01100000 ` (9)
-108 0x94 10010100 (10)
7 0x07 00000111 (11)
100 0x64 01100100 d
105 0x69 01101001 i
103 0x67 01100111 g
117 0x75 01110101 u
97 0x61 01100001 a
103 0x67 01100111 g
101 0x65 01100101 e
74 0x4A 01001010 J (12)
0 0x00 00000000
0 0x00 00000000
1 0x01 00000001
-126 0x82 10000010
108 0x6C 01101100 l
30 0x1E 00011110
119 0x77 01110111 w
-82 0xAE 10101110
67 0x43 01000011 C (13)
20 0x14 00010100 (14)
106 0x6A 01101010 j
97 0x61 01100001 a
118 0x76 01110110 v
97 0x61 01100001 a
46 0x2E 00101110 .
109 0x6D 01101101 m
97 0x61 01100001 a
116 0x74 01110100 t
104 0x68 01101000 h
46 0x2E 00101110 .
66 0x42 01000010 B
105 0x69 01101001 i
103 0x67 01100111 g
68 0x44 01000100 D
101 0x65 01100101 e
99 0x63 01100011 c
105 0x69 01101001 i
109 0x6D 01101101 m
97 0x61 01100001 a
108 0x6C 01101100 l
-111 0x91 10010001 (15)
5 0x05 00000101 (16)
118 0x76 01110110 v
97 0x61 01100001 a
108 0x6C 01101100 l
117 0x75 01110101 u
101 0x65 01100101 e
97 0x61 01100001 a (17)
7 0x07 00000111 (18)
49 0x31 00110001 1
50 0x32 00110010 2
51 0x33 00110011 3
52 0x34 00110100 4
46 0x2E 00101110 .
53 0x35 00110101 5
55 0x37 00110111 7
1 | 数据 |
2 | 类型声明,声明这是一个实例对象。关于类型进行 Hessian 编码的详细解释请看 Hessian 协议解释与实战(四):数组与集合。后面不再赘述。 |
3 | 类型名称,Hessian 直接将类型名称编码为字符串,字符串长度小于 32 时,直接使用 int 后八位。 |
4 | 字段数量 |
5 | 第一个字段的名称: id ; |
6 | 第二个字段的名称: name ; |
7 | 第三个字段的名称: birthday ; |
8 | 第四个字段的名称: money ; |
9 | 类型应用标志符 + 类型编号; |
10 | 第一个字段 id 的值 4 ; |
11 | 第二个字段 name 的值,第一个字节是长度标识符。Hessian 对字符串的编码处理比较特殊,详情请看: Hessian 协议解释与实战(三):字符串。个人觉得,不如直接使用 UTF-8 编码简单。 |
12 | 第三个字段 birthday 的值,首位是日期标识符,后面紧跟八个字节表示精确到毫秒的时间戳; |
13 | 开始序列化第四个字段。这里与 ① 相同,都是类型声明,声明这是一个实例对象。 |
14 | 与 ② 相同,都是类型名称,Hessian 直接将类型名称编码为字符串。 |
15 | 字段数量 |
16 | 字段名称 |
17 | 与 ⑧ 相同,类型应用标志符 + 类型编号; |
18 | BigDecimal 的 value 字段的值。在 Hessian 原始库中使用 StringValueSerializer 来序列化 BigDecimal 。 |
序列化集合
BigDecimal money = new BigDecimal("1234.56789")
.setScale(2, BigDecimal.ROUND_HALF_UP);
int id = 4;
String name = "diguage";
Date date = new Date();
User user = new User(id, name, date, money);
List<User> userList = new ArrayList<>();
userList.add(user);
userList.add(user);
// 序列化 userList
JSON
[
{
"id": 4,
"name": "diguage",
"birthday": "2022-08-05 11:48:32",
"money": 1234.57
},
{
"id": 4,
"name": "diguage",
"birthday": "2022-08-05 11:48:32",
"money": 1234.57
}
]
MessagePack
== Object: java.util.ArrayList ==
== Generic: com.diguage.User ==
== object: json length=151(下面是格式化代码) ==
[
{
"id": 4,
"name": "diguage",
"birthday": "2022-08-05 11:48:32",
"money": 1234.57
},
{
"id": 4,
"name": "diguage",
"birthday": "2022-08-05 11:48:32",
"money": 1234.57
}
]
== object: msgpack result ==
.... 0 ~ 55 ....
-110 0x92 10010010 (1)
-108 0x94 10010100 (2)
4 0x04 00000100 (3)
-89 0xA7 10100111 (4)
100 0x64 01100100 d
105 0x69 01101001 i
103 0x67 01100111 g
117 0x75 01110101 u
97 0x61 01100001 a
103 0x67 01100111 g
101 0x65 01100101 e
-49 0xCF 11001111 (5)
0 0x00 00000000
0 0x00 00000000
1 0x01 00000001
-126 0x82 10000010
108 0x6C 01101100 l
19 0x13 00010011
89 0x59 01011001 Y
-112 0x90 10010000
-89 0xA7 10100111 (6)
49 0x31 00110001 1
50 0x32 00110010 2
51 0x33 00110011 3
52 0x34 00110100 4
46 0x2E 00101110 .
53 0x35 00110101 5
55 0x37 00110111 7
-108 0x94 10010100 (2)
4 0x04 00000100 (3)
-89 0xA7 10100111 (4)
100 0x64 01100100 d
105 0x69 01101001 i
103 0x67 01100111 g
117 0x75 01110101 u
97 0x61 01100001 a
103 0x67 01100111 g
101 0x65 01100101 e
-49 0xCF 11001111 (5)
0 0x00 00000000
0 0x00 00000000
1 0x01 00000001
-126 0x82 10000010
108 0x6C 01101100 l
19 0x13 00010011
89 0x59 01011001 Y
-112 0x90 10010000
-89 0xA7 10100111 (6)
49 0x31 00110001 1
50 0x32 00110010 2
51 0x33 00110011 3
52 0x34 00110100 4
46 0x2E 00101110 .
53 0x35 00110101 5
55 0x37 00110111 7
1 | 表示有两个元素。 |
2 | 表示一个对象的开始,同时也表示有四个字段; |
3 | 第一个字段 id 的值 4 ; |
4 | 第二个字段 name 类型及字段长度 7,MsgPack 直接使用 UTF-8 对字符串进行编码;关于字符串编码,请看: 细说编码与字符集。 |
5 | 第三个字段 birthday 类型,日期类型,后面紧跟八个字节表示精确到毫秒的时间戳; |
6 | 第四个字段 money 类型及字段长度。在 MsgPack 原生的 Jar 包,提供了 BigDecimalTemplate 来处理 BigDecimal 数据,它将 BigDecimal 处理成字符串。所以,这里的类型和第二个字段 name 的类型是一样的。 |
Hessian
== Object: java.util.ArrayList ==
== Generic: com.diguage.User ==
== object: json length=151(下面是格式化代码) ==
[
{
"id": 4,
"name": "diguage",
"birthday": "2022-08-05 11:48:32",
"money": 1234.57
},
{
"id": 4,
"name": "diguage",
"birthday": "2022-08-05 11:48:32",
"money": 1234.57
}
]
== object: hessian result ==
.... 0 ~ 102 ....
122 0x7A 01111010 z (1)
67 0x43 01000011 C (2)
16 0x10 00010000 (3)
99 0x63 01100011 c
111 0x6F 01101111 o
109 0x6D 01101101 m
46 0x2E 00101110 .
100 0x64 01100100 d
105 0x69 01101001 i
103 0x67 01100111 g
117 0x75 01110101 u
97 0x61 01100001 a
103 0x67 01100111 g
101 0x65 01100101 e
46 0x2E 00101110 .
85 0x55 01010101 U
115 0x73 01110011 s
101 0x65 01100101 e
114 0x72 01110010 r
-108 0x94 10010100 (4)
2 0x02 00000010 (5)
105 0x69 01101001 i
100 0x64 01100100 d
4 0x04 00000100 (6)
110 0x6E 01101110 n
97 0x61 01100001 a
109 0x6D 01101101 m
101 0x65 01100101 e
8 0x08 00001000 (7)
98 0x62 01100010 b
105 0x69 01101001 i
114 0x72 01110010 r
116 0x74 01110100 t
104 0x68 01101000 h
100 0x64 01100100 d
97 0x61 01100001 a
121 0x79 01111001 y
5 0x05 00000101 (8)
109 0x6D 01101101 m
111 0x6F 01101111 o
110 0x6E 01101110 n
101 0x65 01100101 e
121 0x79 01111001 y
96 0x60 01100000 ` (9)
-108 0x94 10010100 (10)
7 0x07 00000111 (11)
100 0x64 01100100 d
105 0x69 01101001 i
103 0x67 01100111 g
117 0x75 01110101 u
97 0x61 01100001 a
103 0x67 01100111 g
101 0x65 01100101 e
74 0x4A 01001010 J (12)
0 0x00 00000000
0 0x00 00000000
1 0x01 00000001
-126 0x82 10000010
108 0x6C 01101100 l
30 0x1E 00011110
119 0x77 01110111 w
-82 0xAE 10101110
67 0x43 01000011 C (13)
20 0x14 00010100 (14)
106 0x6A 01101010 j
97 0x61 01100001 a
118 0x76 01110110 v
97 0x61 01100001 a
46 0x2E 00101110 .
109 0x6D 01101101 m
97 0x61 01100001 a
116 0x74 01110100 t
104 0x68 01101000 h
46 0x2E 00101110 .
66 0x42 01000010 B
105 0x69 01101001 i
103 0x67 01100111 g
68 0x44 01000100 D
101 0x65 01100101 e
99 0x63 01100011 c
105 0x69 01101001 i
109 0x6D 01101101 m
97 0x61 01100001 a
108 0x6C 01101100 l
-111 0x91 10010001 (15)
5 0x05 00000101 (16)
118 0x76 01110110 v
97 0x61 01100001 a
108 0x6C 01101100 l
117 0x75 01110101 u
101 0x65 01100101 e
97 0x61 01100001 a (17)
7 0x07 00000111 (18)
49 0x31 00110001 1
50 0x32 00110010 2
51 0x33 00110011 3
52 0x34 00110100 4
46 0x2E 00101110 .
53 0x35 00110101 5
55 0x37 00110111 7
81 0x51 01010001 Q (19)
-111 0x91 10010001 (20)
1 | ArrayList 前置标志符 |
2 | 类型声明,声明这是一个实例对象。关于类型进行 Hessian 编码的详细解释请看 Hessian 协议解释与实战(四):数组与集合。后面不再赘述。 |
3 | 类型名称,Hessian 直接将类型名称编码为字符串,字符串长度小于 32 时,直接使用 int 后八位。 |
4 | 字段数量 |
5 | 第一个字段的名称: id ; |
6 | 第二个字段的名称: name ; |
7 | 第三个字段的名称: birthday ; |
8 | 第四个字段的名称: money ; |
9 | 类型应用标志符 + 类型编号; |
10 | 第一个字段 id 的值 4 ; |
11 | 第二个字段 name 的值,第一个字节是长度标识符。Hessian 对字符串的编码处理比较特殊,详情请看: Hessian 协议解释与实战(三):字符串。个人觉得,不如直接使用 UTF-8 编码简单。 |
12 | 第三个字段 birthday 的值,首位是日期标识符,后面紧跟八个字节表示精确到毫秒的时间戳; |
13 | 开始序列化第四个字段。这里与 ① 相同,都是类型声明,声明这是一个实例对象。 |
14 | 与 ② 相同,都是类型名称,Hessian 直接将类型名称编码为字符串。 |
15 | 字段数量 |
16 | 字段名称 |
17 | 与 ⑧ 相同,类型应用标志符 + 类型编号; |
18 | BigDecimal 的 value 字段的值。在 Hessian 原始库中使用 StringValueSerializer 来序列化 BigDecimal 。 |
19 | 实例引用前置标志符 |
20 | 实例编号 |
数据对比
测试使用的对象有六十多个字段。由于涉及公司内部信息,这里不再展示。仅贴出数据:
序列化体积
协议类型 | Hessian | MessagePack | JSON |
---|---|---|---|
字节长度 | 2108 | 711 | 1399 |
性能测试
对 Hessian 与 MessagePack 做了一个序列化性能测试,结果如下:
Benchmark Mode Cnt Score Error Units
Codec.empty thrpt 25 254056164.437 ± 53520956.815 ops/s
Codec.fastjson thrpt 25 347935.374 ± 9692.241 ops/s
Codec.hessian thrpt 25 117254.446 ± 8526.757 ops/s
Codec.jackson thrpt 25 164937.454 ± 9856.041 ops/s
Codec.msgpack thrpt 25 232443.559 ± 9246.765 ops/s
小结
没想到 Hessian 竟然比 Jackson 还要慢,竟然是所有测评中,吞吐量最低的;
没想到 Hessian 体积竟然比 JSON 还要大。始料未及!
Fastjson 竟然比 Jackson 快一倍还多,有些不可思议;
总结
Hessian 可以“自证”,需要保存类型及字段信息,所以,体积可能较大;
Hessian 在序列化相同字段和实例时,更有优势;(重复字符串不作为重复实例处理)
Hessian 经受住了更多大规模场景的检验;
MessagePack 无法自证,所以体积较小;
MessagePack 在处理相同对象时,没有做优化;
MessagePack 与 Hessian 都对数据做了尽可能的瘦身;
JSON 可读性更好,但是体积相对较大,效率较差;
您还有什么观点或看法?欢迎留言讨论