几种常用序列化和反序列化方法

更新时间:2023-05-11 13:13:40 阅读: 评论:0

⼏种常⽤序列化和反序列化⽅法
摘要
序列化和反序列化⼏乎是⼯程师们每天都要⾯对的事情,但是要精确掌握这两个概念并不容易:⼀⽅⾯,它们往往作为框架的⼀部分出现⽽湮没在框架之中;另⼀⽅⾯,它们会以其他更容易理解的概念出现,例如加密、持久化。然⽽,序列化和反序列化的选型却是系统设计或重构⼀个重要的环节,在分布式、⼤数据量系统设计⾥⾯更为显著。恰当的序列化协议不仅可以提⾼系统的通⽤性、强健性、安全性、优化系统性能,⽽且会让系统更加易于调试、便于扩展。本⽂从多个⾓度去分析和讲解“序列化和反序列化”,并对⽐了当前流⾏的⼏种序列化协议,期望对读者做序列化选型有所帮助。
简介
⽂章作者服务于美团推荐与个性化组,该组致⼒于为美团⽤户提供每天billion级别的⾼质量个性化推荐以及排序服务。从Terabyte级别的⽤户⾏为数据,到Gigabyte级别的Deal/Poi数据;从对实时性要求毫秒以内的⽤户实时地理位置数据,到定期后台job数据,推荐与重排序系统需要多种类型的数据服务。推荐与重排序系统客户包括各种内部服务、美团客户端、美团⽹站。为了提供⾼质量的数据服务,为了实现与上下游各系统进⾏良好的对接,序列化和反序列化的选型往往是我们做系统设计的⼀个重要考虑因素。
本⽂内容按如下⽅式组织:
⼀、定义以及相关概念
互联⽹的产⽣带来了机器间通讯的需求,⽽互联通讯的双⽅需要采⽤约定的协议,序列化和反序列化属于通讯协议的⼀部分。通讯协议往往采⽤分层模型,不同模型每层的功能定义以及颗粒度不同,例如:TCP/IP协议是⼀个四层协议,⽽OSI模型却是七层协议模型。在OSI七层协议模型中展现层(Prentation Layer)的主要功能是把应⽤层的对象转换成⼀段连续的⼆进制串,或者反过来,把⼆进制串转换成应⽤层的对象–这两个功能就是序列化和反序列化。⼀般⽽⾔,TCP/IP协议的应⽤层对应与OSI七层协议模型的应⽤层,展⽰层和会话层,所以序列化协议属于TCP/IP协议应⽤层的⼀部分。本⽂对序列化协议的讲解主要基于OSI七层协议模型。
数据结构、对象与⼆进制串
不同的计算机语⾔中,数据结构,对象以及⼆进制串的表⽰⽅式并不相同。
数据结构和对象:对于类似Java这种完全⾯向对象的语⾔,⼯程师所操作的⼀切都是对象(Object),来⾃于类的实例化。在Java语⾔中最接近数据结构的概念,就是POJO(Plain Old Java Object)或者Javabean--那些只有tter/getter⽅法的类。⽽在C++这种半⾯向对象的语⾔中,数据结构和struct对应,对象和class对应。
⼆进制串:序列化所⽣成的⼆进制串指的是存储在内存中的⼀块数据。C++语⾔具有内存操作符,所以⼆进制串的概念容易理解,例
如,C++语⾔的字符串可以直接被传输层使⽤,因为其本质上就是以'\0'结尾的存储在内存中的⼆进制串。在Java语⾔⾥⾯,⼆进制串的概念容易和String混淆。实际上String 是Java的⼀等公民,是⼀种特殊对象(Object)。对于跨语⾔间的通讯,序列化后的数据当然不能是某种语⾔的特殊数据类型。⼆进制串在Java⾥⾯所指的是byte[],byte是Java的8中原⽣数据类型之⼀(Primitive data types)。
⼆、序列化协议特性
每种序列化协议都有优点和缺点,它们在设计之初有⾃⼰独特的应⽤场景。在系统设计的过程中,需
要考虑序列化需求的⽅⽅⾯⾯,综合对⽐各种序列化协议的特性,最终给出⼀个折衷的⽅案。
通⽤性
通⽤性有两个层⾯的意义:
第⼀、技术层⾯,序列化协议是否⽀持跨平台、跨语⾔。如果不⽀持,在技术层⾯上的通⽤性就⼤⼤降低了。
第⼆、流⾏程度,序列化和反序列化需要多⽅参与,很少⼈使⽤的协议往往意味着昂贵的学习成本;另⼀⽅⾯,流⾏度低的协议,往往缺乏稳定⽽成熟的跨语⾔、跨平台的公共包。
强健性/鲁棒性
以下两个⽅⾯的原因会导致协议不够强健:
第⼀、成熟度不够,⼀个协议从制定到实施,到最后成熟往往是⼀个漫长的阶段。协议的强健性依赖于⼤量⽽全⾯的测试,对于致⼒于提供⾼质量服务的系统,采⽤处于测试阶段的序列化协议会带来很⾼的风险。
第⼆、语⾔/平台的不公平性。为了⽀持跨语⾔、跨平台的功能,序列化协议的制定者需要做⼤量的⼯作;但是,当所⽀持的语⾔或者平台之间存在难以调和的特性的时候,协议制定者需要做⼀个艰难的决定–⽀持更多⼈使⽤的语⾔/平台,亦或⽀持更多的语⾔/平台⽽放弃某个特性。当协议的制定者决定为某种语⾔或平台提供更多⽀持的时候,对于使⽤者⽽⾔,协议的强健性就被牺牲了。
可调试性/可读性
序列化和反序列化的数据正确性和业务正确性的调试往往需要很长的时间,良好的调试机制会⼤⼤提⾼开发效率。序列化后的⼆进制串往往不具备⼈眼可读性,为了验证序列化结果的正确性,写⼊⽅不得同时撰写反序列化程序,或提供⼀个查询平台–这⽐较费时;另⼀⽅⾯,如果读取⽅未能成功实现反序列化,这将给问题查找带来了很⼤的挑战–难以定位是由于⾃⾝的反序列化程序的bug所导致还是由于写⼊⽅序列化后的错误数据所导致。对于跨公司间的调试,由于以下原因,问题会显得更严重:
第⼀、⽀持不到位,跨公司调试在问题出现后可能得不到及时的⽀持,这⼤⼤延长了调试周期。
第⼆、访问限制,调试阶段的查询平台未必对外公开,这增加了读取⽅的验证难度。
如果序列化后的数据⼈眼可读,这将⼤⼤提⾼调试效率, XML和JSON就具有⼈眼可读的优点。
性能
性能包括两个⽅⾯,时间复杂度和空间复杂度:
第⼀、空间开销(Verbosity),序列化需要在原有的数据上加上描述字段,以为反序列化解析之⽤。如果序列化过程引⼊的额外开销过⾼,可能会导致过⼤的⽹络,磁盘等各⽅⾯的压⼒。对于海量分布式存储系统,数据量往往以TB为单位,巨⼤的的额外空间开销意味着⾼昂的成本。
第⼆、时间开销(Complexity),复杂的序列化协议会导致较长的解析时间,这可能会使得序列化和反序列化阶段成为整个系统的瓶颈。
可扩展性/兼容性
移动互联时代,业务系统需求的更新周期变得更快,新的需求不断涌现,⽽⽼的系统还是需要继续维护。如果序列化协议具有良好的可扩展性,⽀持⾃动增加新的业务字段,⽽不影响⽼的服务,这将⼤⼤提供系统的灵活度。
安全性/访问限制
在序列化选型的过程中,安全性的考虑往往发⽣在跨局域⽹访问的场景。当通讯发⽣在公司之间或者跨机房的时候,出于安全的考虑,对于跨局域⽹的访问往往被限制为基于HTTP/HTTPS的80和443端⼝。如果使⽤的序列化协议没有兼容⽽成熟的HTTP传输层框架⽀持,可能会导致以下三种结果之⼀:
第⼀、因为访问限制⽽降低服务可⽤性。
第⼆、被迫重新实现安全协议⽽导致实施成本⼤⼤提⾼。
第三、开放更多的防⽕墙端⼝和协议访问,⽽牺牲安全性。
三、序列化和反序列化的组件
典型的序列化和反序列化过程往往需要如下组件:
序列化组件与数据库访问组件的对⽐
数据库访问对于很多⼯程师来说相对熟悉,所⽤到的组件也相对容易理解。下表类⽐了序列化过程中⽤到的部分组件和数据库访问组件的对应关系,以便于⼤家更好的把握序列化相关组件的概念。
序列化组件
数据库组件
说明
IDL DDL⽤于建表或者模型的语⾔
DL file DB Schema表创建⽂件或模型⽂件
Stub/Skeleton lib O/R mapping将class和Table或者数据模型进⾏映射
四、⼏种常见的序列化和反序列化协议
互联⽹早期的序列化协议主要有COM和CORBA。
COM主要⽤于Windows平台,并没有真正实现跨平台,另外COM的序列化的原理利⽤了编译器中虚表,使得其学习成本巨⼤(想⼀下这个场景,⼯程师需要是简单的序列化协议,但却要先掌握语⾔编译器)。由于序列化的数据与编译器紧耦合,扩展属性⾮常⿇烦。
CORBA是早期⽐较好的实现了跨平台,跨语⾔的序列化协议。COBRA的主要问题是参与⽅过多带来的版本过多,版本之间兼容性较差,以及使⽤复杂晦涩。这些政治经济,技术实现以及早期设计不成熟的问题,最终导致COBRA的渐渐消亡。J2SE 1.3之后的版本提供了基于CORBA协议的RMI-IIOP技术,这使得Java开发者可以采⽤纯粹的Java语⾔进⾏CORBA的开发。
这⾥主要介绍和对⽐⼏种当下⽐较流⾏的序列化协议,包括XML、JSON、Protobuf、Thrift和Avro。
⼀个例⼦
如前所述,序列化和反序列化的出现往往晦涩⽽隐蔽,与其他概念之间往往相互包容。为了更好了让⼤家理解序列化和反序列化的相关概念在每种协议⾥⾯的具体实现,我们将⼀个例⼦穿插在各种序列化协议讲解中。在该例⼦中,我们希望将⼀个⽤户信息在多个系统⾥⾯进⾏传递;在应⽤层,如果采⽤Java语⾔,所⾯对的类对象如下所⽰:
class Address{    private String city;    private String postcode;    private String street;} publicclass UrInfo { private Integer urid;
private String name;    private List<Address> address;}
XML&SOAP
XML是⼀种常⽤的序列化和反序列化协议,具有跨机器,跨语⾔等优点。 XML历史悠久,其1.0版本早在1998年就形成标准,并被⼴泛使⽤⾄今。XML的最初产⽣⽬标是对互联⽹⽂档(Document)进⾏标记,所以它的设计理念中就包含了对于⼈和机器都具备可读性。但是,当这种标记⽂档的设计被⽤来序列化对象的时候,就显得冗长⽽复杂(Verbo and Complex)。 XML本质上是⼀种描述语⾔,并且具有⾃我描述(Self-describing)的属性,所以XML⾃⾝就被⽤于XML序列化的IDL。标准的XML描述格式有两种:DTD(Document Type Definition)和XSD(XML Schema Definition)。作为⼀种⼈眼可读(Human-readable)的描述语⾔,XML被⼴泛使⽤在配置⽂件中,例如O/R mapping、
Spring Bean Configuration File 等。
SOAP(Simple Object Access protocol)是⼀种被⼴泛应⽤的,基于XML为序列化和反序列化协议的结构化消息传递协议。SOAP在互联⽹影响如此⼤,以⾄于我们给基于SOAP的解决⽅案⼀个特定的名称–Web rvice。SOAP虽然可以⽀持多种传输层协议,不过SOAP最常见的使⽤⽅式还是XML+HTTP。SOAP协议的主要接⼝描述语⾔(IDL)是WSDL(Web Service Description Language)。SOAP具有安全、可扩展、跨语⾔、跨平台并⽀持多种传输层协议。如果不考虑跨平台和跨语⾔的需求,XML的在某些语⾔⾥⾯具有⾮常简单易⽤的序列化使⽤⽅法,⽆需IDL⽂件和第三⽅编译器,例如Java+XStream。
⾃我描述与递归
SOAP是⼀种采⽤XML进⾏序列化和反序列化的协议,它的IDL是WSDL. ⽽WSDL的描述⽂件是XSD,⽽XSD⾃⾝是⼀种XML⽂件。这⾥产⽣了⼀种有趣的在数学上称之为“递归”的问题,这种现象往往发⽣在⼀些具有⾃我属性(Self-description)的事物上。
IDL⽂件举例
采⽤WSDL描述上述⽤户基本信息的例⼦如下:
典型应⽤场景和⾮应⽤场景
SOAP协议具有⼴泛的群众基础,基于HTTP的传输协议使得其在穿越防⽕墙时具有良好安全特性,XML所具有的⼈眼可读(Human-readable)特性使得其具有出众的可调试性,互联⽹带宽的⽇益剧增也⼤⼤弥补了其空间开销⼤(Verbo)的缺点。对于在公司之间传输数据量相对⼩或者实时性要求相对低(例如秒级别)的服务是⼀个好的选择。
由于XML的额外空间开销⼤,序列化之后的数据量剧增,对于数据量巨⼤序列持久化应⽤常景,这意味着巨⼤的内存和磁盘开销,不太适合XML。另外,XML的序列化和反序列化的空间和时间开销都⽐较⼤,对于对性能要求在ms级别的服务,不推荐使⽤。WSDL虽然具备了描述对象的能⼒,SOAP的S代表的也是simple,但是SOAP的使⽤绝对不简单。对于习惯于⾯向对象编程的⽤户,WSDL⽂件不直观。
JSON(Javascript Object Notation)
JSON起源于弱类型语⾔Javascript,它的产⽣来⾃于⼀种称之为"Associative array"的概念,其本质是就是采⽤"Attribute-value"的⽅式来描述对象。实际上在Javascript和PHP等弱类型语⾔中,类的描述⽅式就是Associative array。JSON的如下优点,使得它快速成为最⼴泛使⽤的序列化协议之⼀:
1、这种Associative array格式⾮常符合⼯程师对对象的理解。
2、它保持了XML的⼈眼可读(Human-readable)的优点。
4、它具备Javascript的先天性⽀持,所以被⼴泛应⽤于Web browr的应⽤常景中,是Ajax的事实标准协议。
5、与XML相⽐,其协议⽐较简单,解析速度⽐较快。
6、松散的Associative array使得其具有良好的可扩展性和兼容性。
IDL悖论
JSON实在是太简单了,或者说太像各种语⾔⾥⾯的类了,所以采⽤JSON进⾏序列化不需要IDL。这实在是太神奇了,存在⼀种天然的序列化协议,⾃⾝就实现了跨语⾔和跨平台。然⽽事实没有那么神奇,之所以产⽣这种假象,来⾃于两个原因:
第⼀、Associative array在弱类型语⾔⾥⾯就是类的概念,在PHP和Javascript⾥⾯Associative array就是其class的实际实现⽅式,所以在这些弱类型语⾔⾥⾯,JSON得到了⾮常良好的⽀持。
第⼆、IDL的⽬的是撰写IDL⽂件,⽽IDL⽂件被IDL Compiler编译后能够产⽣⼀些代码(Stub/Skeleton),⽽这些代码是真正负责相应的序列化和反序列化⼯作的组件。但是由于Associative array和⼀般语⾔⾥⾯的class太像了,他们之间形成了⼀⼀对应关系,这就使得我们可以采⽤⼀套标准的代码进⾏相应的转化。对于⾃⾝⽀持Associative array的弱类型语⾔,语⾔⾃⾝就具备操作JSON序列化后的数据的能⼒;对于Java这强类型语⾔,可以采⽤反射的⽅式统⼀解决,例如Google提供的Gson。
典型应⽤场景和⾮应⽤场景
JSON在很多应⽤场景中可以替代XML,更简洁并且解析速度更快。典型应⽤场景包括:
1、公司之间传输数据量相对⼩,实时性要求相对低(例如秒级别)的服务。
2、基于Web browr的Ajax请求。
3、由于JSON具有⾮常强的前后兼容性,对于接⼝经常发⽣变化,并对可调式性要求⾼的场景,例如Mobile app与服务端的通讯。
4、由于JSON的典型应⽤场景是JSON+HTTP,适合跨防⽕墙访问。
总的来说,采⽤JSON进⾏序列化的额外空间开销⽐较⼤,对于⼤数据量服务或持久化,这意味着巨⼤的内存和磁盘开销,这种场景不适合。没有统⼀可⽤的IDL降低了对参与⽅的约束,实际操作中往往只能采⽤⽂档⽅式来进⾏约定,这可能会给调试带来⼀些不便,延长开发周期。由于JSON在⼀些语⾔中的序列化和反序列化需要采⽤反射机制,所以在性能要求为ms级别,不建议使⽤。
IDL⽂件举例
以下是UrInfo序列化之后的⼀个例⼦:
{" urid ": 1 ," name ": "messi" ," address ": [{" city ": "北京" ," postcode ": "1000000" ," street ": "wangjingdonglu" }] }
Thrift
Thrift是Facebook开源提供的⼀个⾼性能,轻量级RPC服务框架,其产⽣正是为了满⾜当前⼤数据量、分布式、跨语⾔、跨平台数据通讯的需求。但是,Thrift并不仅仅是序列化协议,⽽是⼀个RPC框架。相对于JSON和XML⽽⾔,Thrift在空间开销和解析性能上有了⽐较⼤的提升,对于对性能要求⽐较⾼的分布式系统,它是⼀个优秀的RPC解决⽅案;但是由于Thrift的序列化被嵌⼊到Thrift框架⾥⾯,Thrift框架本⾝并没有透出序列化和反序列化接⼝,这导致其很难和其他传输层协议共同使⽤(例如HTTP)。
典型应⽤场景和⾮应⽤场景
对于需求为⾼性能,分布式的RPC服务,Thrift是⼀个优秀的解决⽅案。它⽀持众多语⾔和丰富的数据类型,并对于数据字段的增删具有较强的兼容性。所以⾮常适⽤于作为公司内部的⾯向服务构建(SOA)的标准RPC框架。
不过Thrift的⽂档相对⽐较缺乏,⽬前使⽤的群众基础相对较少。另外由于其Server是基于⾃⾝的Socket服务,所以在跨防⽕墙访问时,安全是⼀个顾虑,所以在公司间进⾏通讯时需要谨慎。另外Thrift序列化之后的数据是Binary数组,不具有可读性,调试代码时相对困难。最后,由于Thrift的序列化和框架紧耦合,⽆法⽀持向持久层直接读写数据,所以不适合做数据持久化序列化协议。
IDL⽂件举例
struct Address{    1: required string city;    2: optional string postcode;    3: optional string street;} struct UrInfo{    1: required string urid;    2: required i32 name;    3: optional list<Address> address;}
Protobuf
Protobuf具备了优秀的序列化协议的所需的众多典型特征:
1、标准的IDL和IDL编译器,这使得其对⼯程师⾮常友好。
2、序列化数据⾮常简洁,紧凑,与XML相⽐,其序列化之后的数据量约为1/3到1/10。
3、解析速度⾮常快,⽐对应的XML快约20-100倍。
4、提供了⾮常友好的动态库,使⽤⾮常简介,反序列化只需要⼀⾏代码。
Protobuf是⼀个纯粹的展⽰层协议,可以和各种传输层协议⼀起使⽤;Protobuf的⽂档也⾮常完善。但是由于Protobuf产⽣于Google,所以⽬前其仅仅⽀持Java、C++、Python三种语⾔。另外Protobuf⽀持的数据类型相对较少,不⽀持常量类型。由于其设计的理念是纯粹的展现层协议(Prentation Layer),⽬前并没有⼀个专门⽀持Protobuf的RPC框架。
典型应⽤场景和⾮应⽤场景
Protobuf具有⼴泛的⽤户基础,空间开销⼩以及⾼解析性能是其亮点,⾮常适合于公司内部的对性能要求⾼的RPC调⽤。由于Protobuf提供了标准的IDL以及对应的编译器,其IDL⽂件是参与各⽅的⾮常强的业务约束,另外,Protobuf与传输层⽆关,采⽤HTTP具有良好的跨防⽕墙的访问属性,所以Protobuf也适⽤于公司间对性能要求⽐较⾼的场景。由于其解析性能⾼,序列化后数据量相对少,⾮常适合应⽤层对象的持久化场景。
它的主要问题在于其所⽀持的语⾔相对较少,另外由于没有绑定的标准底层传输层协议,在公司间进⾏传输层协议的调试⼯作相对⿇烦。
IDL⽂件举例
message Address{    required string city=1;        optional string postcode=2;        optional string street=3;}message UrInfo{    required string urid=1;    required string name=2;    repeated Address address=3;}
Avro
Avro的产⽣解决了JSON的冗长和没有IDL的问题,Avro属于Apache Hadoop的⼀个⼦项⽬。 Avro提供两种序列化格式: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解析性能⾼并且序列化之后的数据⾮常简洁,⽐较适合于⾼性能的序列化服务。

本文发布于:2023-05-11 13:13:40,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/883358.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:序列化   协议   系统   数据   调试
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图