Hessian、Msgpack 和 JSON 实例对比

Hessian、Msgpack 和 JSON 实例对比

前段时间,翻译了 Hessian 2.0 的序列化协议,发布在了 Hessian 2.0 序列化协议(中文版)。但是,其中有很多言语不详之处。所以,接下来会用几篇文章来详细解释并实践一下 Hessian 序列化协议,以求做到知其然知其所以然。

目录如下:

  1. Hessian 2.0 序列化协议(中文版) — Hessian 序列化协议的中文翻译版。根据后面的“协议解释与实战”系列文章,增加了协议内容错误提示。

  2. Hessian 协议解释与实战(一):布尔、日期、浮点数与整数 — 介绍布尔型数据、日期类型、浮点类型数据和整数类型数据等四种类型的数据的处理。

  3. Hessian 协议解释与实战(二):长整型、二进制数据与 Null — 介绍长整数类型数据、二进制数据和 null 等三种类型的数据的处理。

  4. Hessian 协议解释与实战(三):字符串 — 专门介绍了关于字符串的处理。由于字符串需要铺垫的基础知识比较多,处理细节也有繁琐,所以单独成篇来介绍。

  5. Hessian 源码分析(Java) — 开始第四篇分析之前,先来介绍一下 Hessian 的源码实现。方便后续展开说明。

  6. Hessian 协议解释与实战(四):数组与集合 — 铺垫了一些关于实例对象的处理,重点介绍关于数组和集合的相关处理。

  7. Hessian 协议解释与实战(五):对象与映射 — 重点介绍关于对象与映射的相关处理。

  8. Hessian、Msgpack 和 JSON 实例对比 — 用实例对比 JSON、Hessian 和 MessagePack 的区别。

  9. 未完待续,敬请继续关注 "地瓜哥"博客网

本文用实际来对比一下 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下面是格式化代码 ==
{
  "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下面是格式化代码 ==
{
  "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与 ⑧ 相同,类型应用标志符 + 类型编号;
18BigDecimalvalue 字段的值。在 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)
1ArrayList 前置标志符
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与 ⑧ 相同,类型应用标志符 + 类型编号;
18BigDecimalvalue 字段的值。在 Hessian 原始库中使用 StringValueSerializer 来序列化 BigDecimal
19实例引用前置标志符
20实例编号

数据对比

测试使用的对象有六十多个字段。由于涉及公司内部信息,这里不再展示。仅贴出数据:

序列化体积

协议类型HessianMessagePackJSON

字节长度

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

小结

  1. 没想到 Hessian 竟然比 Jackson 还要慢,竟然是所有测评中,吞吐量最低的;

  2. 没想到 Hessian 体积竟然比 JSON 还要大。始料未及!

  3. Fastjson 竟然比 Jackson 快一倍还多,有些不可思议;

总结

  1. Hessian 可以“自证”,需要保存类型及字段信息,所以,体积可能较大;

  2. Hessian 在序列化相同字段和实例时,更有优势;(重复字符串不作为重复实例处理)

  3. Hessian 经受住了更多大规模场景的检验;

  4. MessagePack 无法自证,所以体积较小;

  5. MessagePack 在处理相同对象时,没有做优化;

  6. MessagePack 与 Hessian 都对数据做了尽可能的瘦身;

  7. JSON 可读性更好,但是体积相对较大,效率较差;

您还有什么观点或看法?欢迎留言讨论