序列化与反序列化:通过网络传输结构化的数据

一、前言

在TCP的连接上,它传输数据的基本形式就是二进制流,也就是一段一段的1和0。在一般编程语言或者网络框架提供的API中,传输数据的基本形式是字节,也就是Byte。一个字节就是8个二进制位,8个Bit,所以在这里,二进制流和字节流本质上是一样的。对于我们编写的程序来说,它需要通过网络传输的数据是结构化的数据,比如,一条命令、一段文本或者一条消息。对应代码中,这些结构化的数据都可以用一个类或者一个结构体来表示。

要想使用网络框架的API来传输结构化的数据,必须得先实现结构化的数据与字节流之间的双向转换。这种将结构化数据转换成字节流的过程,称为序列化,反过来转换,就是反序列化。序列化的用途除了用于在网络上传输数据以外,另外一个重要用途是,将结构化数据保存在文件中,因为文件内保存数据的形式也是二进制序列。

二、选择哪种序列化实现

Java和Go语言都内置了序列化实现,也有一些流行的开源序列化实现,比如,Googel的Protobuf、Kryo、Hessian等;此外,像JSON、XML这些标准的数据格式,也可以作为一种序列化实现来使用。当然,也可以自己来实现私有的序列化实现。

面对这么多种序列化实现,需要权衡几个因素:

  • 序列化后的数据最好是易于人类阅读的
  • 实现的复杂度是否足够低
  • 序列化和反序列化的速度越快越好
  • 序列化后的信息密度越大越好,也就是说,同样的一个结构化数据,序列化之后占用的存储空间越小越好

不存在一种序列化实现在这几个方面都是最优的,像JSON、XML可读性最好,但信息密度也最低。像Kryo、Hessian这些通用的二进制序列化,适用范围广,适用简单,性能比JSON、XML要好一些,但肯定不如专用的序列化实现。

对于一些强业务类系统,比如电商、社交应用系统,特点是业务复杂,需求变化快,但是对性能的要求没有那么苛刻。推荐使用JSON这种实现简单,数据可读性好的序列化实现,无论是接口调试还是排查问题都非常方便,付出的代价就是多一点点CPU时间和存储空间。

如果JSON序列化的性能达不到系统的要求,可以采用性能更好的二进制序列化实现,实现的复杂度和JSON序列化差不多,但是序列化性能更好,信息密度更高,代价就是失去了可读性。

三、实现高性能的序列化和反序列化

绝大部分系统,使用上面两种通用的序列化实现都可以满足需求,而像消息队列这种用于解决通信问题的中间件,对性能要求非常高,通用的序列化实现达不到性能要求,所以,很多消息队列选择自己实现高性能的专用序列化和反序列化

使用专用的序列化方法,可以提高序列化性能,并有效减小序列化后的字节长度。不必考虑通用性,比如,可以固定字段的顺序,这样在序列化后的字节里面就不必包含字段名,只要字段值就可,不同类型的数据也可以做针对性的优化:

03   | 08 7a 68 61 6e 67 73 61 6e | 17 | 01
User |    z  h  a  n  g  s  a  n  | 23 | true

1.首先我们需要标识一下这个对象的类型,这里面我们用一个字节来表示类型,比如用 03 表示这是一个 User 类型的对象。
2.我们约定,按照 name、age、married 这个固定顺序来序列化这三个属性。按照顺序,第一个字段是 name,我们不存字段名,直接存字段值“zhangsan”就可以了,由于名字的长度不固定,我们用第一个字节 08 表示这个名字的长度是 8 个字节,后面的 8 个字节就是 zhangsan。
3.第二个字段是年龄,我们直接用一个字节表示就可以了,23 的 16 进制是 17 。
4.最后一个字段是婚姻状态,我们用一个字节来表示,01 表示已婚,00 表示未婚,这里面保存一个 01。

可以看到,同样的一个User对象,JSON序列化后({"name":"zhangsan","age":"23","married":"true"})需要47个字节,这里只要12个字节就够了。

专用的序列化方法显然更高效,序列化出来的字节更少,在网络传输过程中的速度也更快。但缺点是,需要为每种对象类型定义专门的序列化和反序列化方法,实现起来太复杂了,大部分情况下是不划算的。

四、总结

  • 进程之间要通过网络传输结构化的数据,需要通过序列化和反序列化来实现结构化数据和二进制数据的双向转换。在选择序列化实现的时候,需要综合考虑数据可读性,实现复杂度,性能和信息密度这四个因素。
  • 大多数情况下,选择一个高性能的通用序列化框架都可以满足要求,在性能可以满足需求的前提下,推荐优先选择 JSON 这种可读性好的序列化方法。
  • 如果说我们需要超高的性能,或者是带宽有限的情况下,可以使用专用的序列化方法,来提升序列化性能,节省传输流量。不过实现起来很复杂,大部分情况下并不划算。

问题:在内存里存放的任何数据,它最基础的存储单元也是二进制比特,也就是说,我们应用程序操作的对象,它在内存中也是使用二进制存储的,既然都是二进制,为什么不能直接把内存中,对象对应的二进制数据直接通过网络发送出去,或者保存在文件中呢?为什么还需要序列化和反序列化呢?

  • 内存里存的东西,不通用, 不同系统, 不同语言的组织可能都是不一样的, 而且还存在很多引用, 指针,并不是直接数据块。内存中的对象数据应该具有语言独特性,例如表达相同业务的User对象(id/name/age字段),Java和PHP在内存中的数据格式应该不一样的,如果直接用内存中的数据,可能会造成语言不通。通常两个服务之间没有严格要求语言必须一致,只要对序列化的数据格式进行了协商,任何2个语言直接都可以进行序列化传输、接收。
  • 序列化, 反序列化, 其实是约定一种标准吧, 大家都按这个标准去弄, 就能跨平台 , 跨语言。
  • 虽然都是二进制的数据,但是序列化的二进制数据是通过一定的协议将数据字段进行拼接。第一个优势是:不同的语言都可以遵循这种协议进行解析,实现了跨语言。第二个优势是:这种数据可以直接持久化到磁盘,从磁盘读取后也可以通过这个协议解析出来。如果是内存中的数据不能直接存盘的,直接存盘后再读出来我们根本无法辨识这是个什么数据。
原文地址:https://www.cnblogs.com/chjxbt/p/11458815.html