作者: 京东零售 肖梦圆
前序
某一日晚上上线,测试同学在回归项目黄金流程时,有一个工单项目接口报JSF序列化错误,马上升级对应的client包版本,编译部署后错误消失。
线上问题是解决了,但是作为程序员要了解问题发生的原因和本质。但这都是为什么呢?
第一个问题:为什么测试的时候没有发现问题呢?
首先预发环境中,所有项目中的JSF别名和client包都是beta,每天都有项目进行编译部署,这样每个项目获取的都是最新的client包,所以在预发环境测试没有发现
第二个问题:为什么会出现序列化问题?
JDer的开发们都知道JSF接口如果添加字段需要在类的最后进行添加,对此我检查了自己的代码发现我添加的代码也是在类的最后进行添加的,但是特殊之处在于这是一个父类,有子类进行继承
第三个问题:如果在父类上添加一个字段有什么影响呢?
说实话,猛的这么一问,我犹豫了,JDer们都知道JSF的默认序列化使用的是MsgPack,一直都是口口相传说如果client类添加字段必须在类的最后,但是也没人告诉父类添加字段咋办呀,父子类这种场景MsgPack是如何处理序列化和反序列化的?
第四个问题:MsgPack是什么?MsgPack的序列化和反序化是怎么实现的?
对此问题我坦白了,我不知道;是否有很多JDer跟我对于MsgPack的认识仅限于名字的吗,更别提是如何实现序列化和反序列化了
到此我已经积累了这么多问题了,是时候努力了解一下MsgPack了,看看什么是MsgPack,为什么JSF的默认序列化选择MsgPack呢?
msgpack介绍
官网地址: https://msgpack.org/
官方介绍:
It's like JSON. but fast and small.
翻译如下:
这就像JSON,但更快更小
MessagePack 是一种高效的二进制序列化格式。它允许您在多种语言(如 JSON)之间交换数据。但是速度更快,体积更小。小整数被编码成一个字节,而典型的短字符串除了字符串本身之外只需要一个额外的字节。
JSON格式占用27字节,msgpack只占用18字节
msgpack 核心压缩规范
msgpack制定了压缩规范,这使得msgpack更小更快。我们先了解一下核心规范:
format name | first byte (in binary) | first byte (in hex) |
---|---|---|
positive fixint | 0xxxxxxx | 0x00 - 0x7f |
fixmap | 1000xxxx | 0x80 - 0x8f |
fixarray | 1001xxxx | 0x90 - 0x9f |
fixstr | 101xxxxx | 0xa0 - 0xbf |
nil | 11000000 | 0xc0 |
(never used) | 11000001 | 0xc1 |
false | 11000010 | 0xc2 |
true | 11000011 | 0xc3 |
bin 8 | 11000100 | 0xc4 |
bin 16 | 11000101 | 0xc5 |
bin 32 | 11000110 | 0xc6 |
ext 8 | 11000111 | 0xc7 |
ext 16 | 11001000 | 0xc8 |
ext 32 | 11001001 | 0xc9 |
float 32 | 11001010 | 0xca |
float 64 | 11001011 | 0xcb |
uint 8 | 11001100 | 0xcc |
uint 16 | 11001101 | 0xcd |
uint 32 | 11001110 | 0xce |
uint 64 | 11001111 | 0xcf |
int 8 | 11010000 | 0xd0 |
int 16 | 11010001 | 0xd1 |
int 32 | 11010010 | 0xd2 |
int 64 | 11010011 | 0xd3 |
fixext 1 | 11010100 | 0xd4 |
fixext 2 | 11010101 | 0xd5 |
fixext 4 | 11010110 | 0xd6 |
fixext 8 | 11010111 | 0xd7 |
fixext 16 | 11011000 | 0xd8 |
str 8 | 11011001 | 0xd9 |
str 16 | 11011010 | 0xda |
str 32 | 11011011 | 0xdb |
array 16 | 11011100 | 0xdc |
array 32 | 11011101 | 0xdd |
map 16 | 11011110 | 0xde |
map 32 | 11011111 | 0xdf |
negative fixint | 111xxxxx | 0xe0 - 0xff |
示例解读:
json串:{"compact":true,"schema":0}
对应的msgpack为:82 a7 63 6f 6d 70 61 63 74 c3 a6 73 63 68 65 6d 61 00
第一个82,查看规范表,落在fixmap上,fixmap的范围:0x80 - 0x8f,表示这是一个map结构,长度为2
后面一个为a7,查看规范表,落在fixstr的范围:0xa0 - 0xbf,表示是一个字符串,长度为7,后面7个为字符串内容:63 6f 6d 70 61 63 74 将16进制转化为字符串为:compact
往后一个为:c3,落在true的范围:oxc3
再往后一个为:a6,查看规范表,落在fixstr的范围:0xa0 - 0xbf,表示是一个字符串,长度为6,后面6个字符串内容为:
73 63 68 65 6d 61,将16进制转化为字符串为:schema
最后一个为:00,查看规范表,落在positive fixint,表示一个数字,将16进制转为10进制数字为:0
拼装一下{ "compact" : true , "schema" : 0 }
我们看一下官方给出的stringformat示意图:
对于上面的问题,一个长度大于15(也就是长度无法用4bit表示)的string是这么表示的:用指定字节0xD9表示后面的内容是一个长度用8bit表示的string,比如一个160个字符长度的字符串,它的头信息就可以表示为D9A0。
举一个长字符串的例子:
{"name":"fatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfatherfath