序列化
2021-06-14 08:24:20 阿炯

序列化(serialization)在计算机科学的资料处理中,是指将数据结构或对象状态转换成可取用格式例如存成文件,存于缓冲,或经由网络中发送,以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化也称为解编组、deserialization、unmarshalling。

序列化在计算机科学中通常有以下定义:
1、对同步控制而言,表示强制在同一时间内进行单一访问。
2、在数据储存与发送的部分是指将一个对象存储至一个存储介质,例如文件或是存储器缓冲等,或者透过网络发送资料时进行编码的过程,可以是字节或是XML等格式。而字节的或XML编码格式可以还原完全相等的对象。这程序被应用在不同应用程序之间发送对象,以及服务器将对象存储到文件或数据库。相反的过程又称为反序列化。

序列化: 将数据结构或对象转换成二进制串的过,将程序内的对象转化为字节序列的过程,将一个对象的状态保存起来。
反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程,将字节序列转化为程序内对象的过程,将已经保存的流对象恢复成原来的对象。

序列化的好处:一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),二是利用序列化实现远程通信,即在网络上传送对象的字节序列。

串行化和并行化

串行化也叫做序列化,就是把存在于内存的对象数据转化成可以保存成硬盘文件的形式去存储;并行化也叫反序列化,就是把序列化后的硬盘文件加载到内存,重新变成对象数据。

序列化和反序列化的选型却是系统设计或重构一个重要的环节,在分布式、大数据量系统设计里面更为显著。恰当的序列化协议不仅可以提高系统的通用性、强健性、安全性、优化系统性能,而且会让系统更加易于调试、便于扩展。

特点与用途

每种序列化协议都有优点和缺点,它们在设计之初有自己独特的应用场景。在系统设计的过程中,需要考虑序列化需求的方方面面,综合对比各种序列化协议的特性,最终给出一个折衷的方案。

通用性
这有两个层面的意义:
1、技术层面,序列化协议是否支持跨平台、跨语言。如果不支持,在技术层面上的通用性就大大降低了。
2、流行程度,序列化和反序列化需要多方参与,很少人使用的协议往往意味着昂贵的学习成本;另一方面,流行度低的协议,往往缺乏稳定而成熟的跨语言、跨平台的公共包。

强健性
以下两个方面的原因会导致协议不够强健:
1、成熟度不够,一个协议从制定到实施,到最后成熟往往是一个漫长的阶段。协议的强健性依赖于大量而全面的测试,对于致力于提供高质量服务的系统,采用处于测试阶段的序列化协议会带来很高的风险。
2、语言/平台的不公平性。为了支持跨语言、跨平台的功能,序列化协议的制定者需要做大量的工作;但是,当所支持的语言或者平台之间存在难以调和的特性的时候,协议制定者需要做一个艰难的决定–支持更多人使用的语言/平台,亦或支持更多的语言/平台而放弃某个特性。当协议的制定者决定为某种语言或平台提供更多支持的时候,对于使用者而言,协议的强健性就被牺牲了。

可调试
序列化和反序列化的数据正确性和业务正确性的调试往往需要很长的时间,良好的调试机制会大大提高开发效率。序列化后的二进制串往往不具备人眼可读性,为了验证序列化结果的正确性,写入方不得同时撰写反序列化程序,或提供一个查询平台–这比较费时;另一方面,如果读取方未能成功实现反序列化,这将给问题查找带来了很大的挑战–难以定位是由于自身的反序列化程序的bug所导致还是由于写入方序列化后的错误数据所导致。对于跨公司间的调试,由于以下原因,问题会显得更严重:
1、支持不到位,跨公司调试在问题出现后可能得不到及时的支持,这大大延长了调试周期。
2、访问限制,调试阶段的查询平台未必对外公开,这增加了读取方的验证难度。

如果序列化后的数据人眼可读,这将大大提高调试效率, XML和JSON就具有人眼可读的优点。

性能
包括两个方面,时间复杂度和空间复杂度:
1、空间开销Verbosity,序列化需要在原有的数据上加上描述字段,以为反序列化解析之用。如果序列化过程引入的额外开销过高,可能会导致过大的网络,磁盘等各方面的压力。对于海量分布式存储系统,数据量往往以TB为单位,巨大的的额外空间开销意味着高昂的成本。
2、时间开销Complexity,复杂的序列化协议会导致较长的解析时间,这可能会使得序列化和反序列化阶段成为整个系统的瓶颈。

可扩展
移动互联时代,业务系统需求的更新周期变得更快,新的需求不断涌现,而老的系统还是需要继续维护。如果序列化协议具有良好的可扩展性,支持自动增加新的业务字段,而不影响老的服务,这将大大提供系统的灵活度。

安全
在序列化选型的过程中,安全性的考虑往往发生在跨局域网访问的场景。当通讯发生在公司之间或者跨机房的时候,出于安全的考虑,对于跨局域网的访问往往被限制为基于HTTP/HTTPS的80和443端口。

即以下都是需要考虑的方方面面:
经由网络线路传输资料的方法通信。
存储资料的方法在数据库或硬盘。
远程程序调用的方法,例如在SOAP中。
在以组件为基础,例如COM,CORBA的软件工程中,是对象的分布式方法。
检测随时间资料变动的方法。

为了实现上述功能或其一,还必须与硬件结构保持独立性。如为了能最大化分布式的使用,在不同硬件运行的计算机,应该能够可靠地重建序列化资料流,而不依赖于字节序。虽然直接复拷存储器中的数据结构更简便又快速,可是对于其它不同硬件的机器,却无法可靠地运作。以独立于硬件之外的格式来序列化数据结构,要避开字节序、存储器方式、或在不同编程语言中数据结构如何表示等等之类的问题。

对于任何序列化方案的本质来说,因为资料编码是根据定义连续串在一起的,提取序列化数据结构中的某一部分,则需要从头到尾读取整个对象并且重新建构。这样的资料线性在许多应用中是有利的,因为它使输出入接口简单而共同,能被用来保持及传递对象的状态。

要求高性能的应用时,花费精力处理更复杂的非线性存储系统是有其必要意义的。即使在单一机器上,原始的指针对象也非常脆弱无法保存,因为它们指向的标地可能重新加载到内存中的不同地址。为了处理这个问题,序列化过程是这样的一个步骤:将引用的直接指针转换为以名称或位置的间接引用,称之为不挥发unswizzling或者指针不挥发。反序列化过程则包括了称为指针旋转swizzling的反向步骤。由于序列化和反序列化可从共通代码例如,微软MFC中的Serialize函数驱动,所以共通代码可同时进行两次,因此检测要序列化的对象与其先前副本之间的差异,提供下一次这种检测的输入。


序列化和反序列化的组件

典型的序列化和反序列化过程往往需要如下组件:
1、IDLInterface description language文件:参与通讯的各方需要对通讯的内容需要做相关的约定Specifications。为了建立一个与语言和平台无关的约定,这个约定需要采用与具体开发语言、平台无关的语言来进行描述。这种语言被称为接口描述语言IDL,采用IDL撰写的协议约定称之为IDL文件。
2、IDL Compiler:IDL文件中约定的内容为了在各语言和平台可见,需要有一个编译器,将IDL文件转换成各语言对应的动态库。
3、Stub/Skeleton Lib:负责序列化和反序列化的工作代码。Stub是一段部署在分布式系统客户端的代码,一方面接收应用层的参数,并对其序列化后通过底层协议栈发送到服务端,另一方面接收服务端序列化后的结果数据,反序列化后交给客户端应用层;Skeleton部署在服务端,其功能与Stub相反,从传输层接收序列化参数,反序列化后交给服务端应用层,并将应用层的执行结果序列化后最终传送给客户端Stub。
4、Client/Server:指的是应用层程序代码,他们面对的是IDL所生存的特定语言的class或struct。
5、底层协议栈和互联网:序列化之后的数据通过底层的传输层、网络层、链路层以及物理层协议转换成数字信号在互联网中传递。




常见的序列化和反序列化协议

90年代后期开始推动标准序列化的协议:XML可扩展标记语言应用于产生人类可读的文字编码。资料以这样的编码使存续的对象能有效用,无论相对于人是否可阅读与理解,或与编程语言无关地传递给其它信息系统。它缺点是失去了扎实的编码字节流,但截至目前技术上所提供大量的存储和传输容量,使得文件大小的考量,已不同于早期计算机科学的重视程度。

二进制XML被提议作为一种妥协方式,它不能被纯文本编辑器读取,但比一般XML更为结实。在二十一世纪的Ajax技术网页中,XML经常应用于结构化资料在客端和服务端之间的异步传输。相较于XML,JSON是一种轻量级的纯文字替代,也常用于网页应用中的客端-服务端通信。JSON肇基于JavaScript语法所派生,但也广为其它编程语言所支持。与JSON类似的另一个替代方案是YAML,它包含加强序列化的功能,更“人性化”而且更扎实。这些功能包括标记资料类型,支持非层次结构式数据结构,缩进结构化资料的选项以及多种形式的标量资料引用的概念。

另一种可读的序列化格式是属性列表property list。应用在NeXTSTEP、GNUstep和macOS Cocoa环境中。针对于科学使用的大量资料集合,例如气候,海洋模型和卫星数据,已经开发了特定的二进制序列化标准,例如HDF,netCDF和较旧的GRIB。

包括XML、JSON、Protobuf、Thrift、Avro、MessagePack,下面分别做一些简述。

XML&SOAP


XML的最初产生目标是对互联网文档Document进行标记,所以它的设计理念中就包含了对于人和机器都具备可读性。但是,当这种标记文档的设计被用来序列化对象的时候,就显得冗长而复杂Verbose and Complex。XML本质上是一种描述语言,并且具有自我描述Self-describing的属性,所以XML自身就被用于XML序列化的IDL。标准的XML描述格式有两种:DTDDocument Type Definition和XSDXML Schema Definition。作为一种人眼可读Human-readable的描述语言,XML被广泛使用在配置文件中。

SOAPSimple Object Access protocol 是一种被广泛应用的,基于XML为序列化和反序列化协议的结构化消息传递协议。SOAP在互联网影响如此大,以至于我们给基于SOAP的解决方案一个特定的名称–Web service。SOAP虽然可以支持多种传输层协议,不过SOAP最常见的使用方式还是XML+HTTP。SOAP协议的主要接口描述语言IDL是WSDLWeb Service Description Language。SOAP具有安全、可扩展、跨语言、跨平台并支持多种传输层协议。如果不考虑跨平台和跨语言的需求,XML的在某些语言里面具有非常简单易用的序列化使用方法,无需IDL文件和第三方编译器, 例如Java+XStream。

自我描述与递归

SOAP是一种采用XML进行序列化和反序列化的协议,它的IDL是WSDL。而WSDL的描述文件是XSD,而XSD自身是一种XML文件。这里产生了一种有趣的在数学上称之为“递归”的问题,这种现象往往发生在一些具有自我属性Self-description的事物上。SOAP协议具有广泛的群众基础,基于HTTP的传输协议使得其在穿越防火墙时具有良好安全特性,XML所具有的人眼可读Human-readable特性使得其具有出众的可调试性,互联网带宽的日益剧增也大大弥补了其空间开销大Verbose的缺点。对于在公司之间传输数据量相对小或者实时性要求相对低例如秒级别的服务是一个好的选择。

由于XML的额外空间开销大,序列化之后的数据量剧增,对于数据量巨大序列持久化应用常景,这意味着巨大的内存和磁盘开销。另外,XML的序列化和反序列化的空间和时间开销都比较大,对于对性能要求在ms级别的服务,不推荐使用。WSDL虽然具备了描述对象的能力,SOAP的S代表的也是simple,但是SOAP的使用绝对不简单。对于习惯于面向对象编程的用户,WSDL文件不直观。


JSON

JSON(Javascript Object Notation)起源于弱类型语言Javascript, 它的产生来自于一种称之为“Associative array”的概念,其本质是就是采用“Attribute-value”的方式来描述对象。实际上在Javascript和PHP等弱类型语言中,类的描述方式就是Associative array。JSON的如下优点,使得它快速成为最广泛使用的序列化协议之一:
1、这种Associative array格式非常符合工程师对对象的理解。
2、它保持了XML的人眼可读Human-readable的优点。
3、相对于XML而言,序列化后的数据更加简洁。来自于的以下链接的研究表明:XML所产生序列化之后文件的大小接近JSON的两倍http://www.codeproject.com/Articles/604720/JSON-vs-XML-Some-hard-numbers-about-verbosity 。
4、它具备Javascript的先天性支持,所以被广泛应用于Web browser的应用常景中,是Ajax的事实标准协议。
5、与XML相比,其协议比较简单,解析速度比较快。
6、松散的Associative array使得其具有良好的可扩展性和兼容性。

总的来说,采用JSON进行序列化的额外空间开销比较大,对于大数据量服务或持久化,这意味着巨大的内存和磁盘开销,这种场景不适合。没有统一可用的IDL降低了对参与方的约束,实际操作中往往只能采用文档方式来进行约定,这可能会给调试带来一些不便,延长开发周期。由于JSON在一些语言中的序列化和反序列化需要采用反射机制,所以在性能要求为ms级别,不建议使用。


MessagePack


Thrift

Thrift是Facebook开源提供的一个高性能,轻量级RPC服务框架,其产生正是为了满足当前大数据量、分布式、跨语言、跨平台数据通讯的需求。但它并不仅仅是序列化协议,而是一个RPC框架。相对于JSON和XML而言,Thrift在空间开销和解析性能上有了比较大的提升,对于对性能要求比较高的分布式系统,它是一个优秀的RPC解决方案;但是由于Thrift的序列化被嵌入到Thrift框架里面,Thrift框架本身并没有透出序列化和反序列化接口,这导致其很难和其他传输层协议共同使用例如HTTP。

对于需求为高性能,分布式的RPC服务,Thrift是一个优秀的解决方案。它支持众多语言和丰富的数据类型,并对于数据字段的增删具有较强的兼容性。所以非常适用于作为公司内部的面向服务构建SOA的标准RPC框架。

不过Thrift的文档相对比较缺乏,目前使用的群众基础相对较少。另外由于其Server是基于自身的Socket服务,所以在跨防火墙访问时,安全是一个顾虑,所以在公司间进行通讯时需要谨慎。另外Thrift序列化之后的数据是Binary数组,不具有可读性,调试代码时相对困难。最后,由于Thrift的序列化和框架紧耦合,无法支持向持久层直接读写数据,所以不适合做数据持久化序列化协议。


Protobuf

Protobuf具备了优秀的序列化协议的所需的众多典型特征:
1、标准的IDL和IDL编译器,这使得其对工程师非常友好。
2、序列化数据非常简洁,紧凑,与XML相比,其序列化之后的数据量约为1/3到1/10。
3、解析速度非常快,比对应的XML快约20-100倍。
4、提供了非常友好的动态库,使用非常简介,反序列化只需要一行代码。

Protobuf是一个纯粹的展示层协议,可以和各种传输层协议一起使用;Protobuf的文档也非常完善。但由于Protobuf产生于Google,所以目前其仅仅支持Java、C++、Python三种语言。另外Protobuf支持的数据类型相对较少,不支持常量类型。由于其设计的理念是纯粹的展现层协议Presentation Layer,目前并没有一个专门支持Protobuf的RPC框架,有第三方实现了一个gRPC框架。

Protobuf具有广泛的用户基础,空间开销小以及高解析性能是其亮点,非常适合于公司内部的对性能要求高的RPC调用。由于Protobuf提供了标准的IDL以及对应的编译器,其IDL文件是参与各方的非常强的业务约束,另外,Protobuf与传输层无关,采用HTTP具有良好的跨防火墙的访问属性,所以Protobuf也适用于公司间对性能要求比较高的场景。由于其解析性能高,序列化后数据量相对少,非常适合应用层对象的持久化场景。

它的主要问题在于其所支持的语言相对较少,另外由于没有绑定的标准底层传输层协议,在公司间进行传输层协议的调试工作相对麻烦。

Avro

Avro的产生解决了JSON的冗长和没有IDL的问题,Avro属于Apache Hadoop的一个子项目。它提供两种序列化格式:JSON格式或者Binary格式。Binary格式在空间开销和解析性能方面可以和Protobuf媲美,JSON格式方便测试阶段的调试。

Avro支持的数据类型非常丰富,包括C++语言里面的union类型。Avro支持JSON格式的IDL和类似于Thrift和Protobuf的IDL实验阶段,这两者之间可以互转。Schema可以在传输数据的同时发送,加上JSON的自我描述属性,这使得Avro非常适合动态类型语言。Avro在做文件持久化的时候,一般会和Schema一起存储,所以Avro序列化文件自身具有自我描述属性,所以非常适合于做Hive、Pig和MapReduce的持久化数据格式。对于不同版本的Schema,在进行RPC调用的时候,服务端和客户端可以在握手阶段对Schema进行互相确认,大大提高了最终的数据解析速度。

Avro解析性能高并且序列化之后的数据非常简洁,比较适合于高性能的序列化服务。由于Avro目前非JSON格式的IDL处于实验阶段,而JSON格式的IDL对于习惯于静态类型语言的工程师来说不直观。


编程语言Perl对序列化的支持情况

由CPAN所提供的几个Perl模块提供序列化机制,包括了Storable,JSON::XS和FreezeThaw,Data::MessagePack。Storable包括将文件或Perl标量的数据结构,将其序列化和反序列化的功能。除了直接序列化到文件之外,Storable还包含了冻结(freeze)功能,将包装为标量的资料,返回其序列化的副本;并可用解冻(thaw)这个标量来反序列化。这对于以Socket发送复杂的数据结构,或将其存储于数据库中非常有用。

当利用Storable对结构进行序列化时,具备了网络安全性的功能,它们以降低一点性能的成本,将资料存储为任何计算机可读取的格式。这些功能的名称有nstore,nfreeze等。依硬件特定的,首字母“n”函数所序列化的这些结构,则以没有首字母“n”的函数将之反序列化-常态地解冻并截取反序列化结构。