文章做者服务于美团推荐与个性化组,该组致力于为美团用户提供天天billion级别的高质量个性化推荐以及排序服务。从Terabyte级别的用户行为数据,到Gigabyte级别的Deal/Poi数据;从对实时性要求毫秒之内的用户实时地理位置数据,到按期后台job数据,推荐与重排序系统须要多种类型的数据服务。推荐与重排序系统客户包括各类内部服务、美团客户端、美团网站。为了提供高质量的数据服务,为了实现与上下游各系统进行良好的对接,序列化和反序列化的选型每每是咱们作系统设计的一个重要考虑因素。html
本文内容按以下方式组织:java
互联网的产生带来了机器间通信的需求,而互联通信的双方须要采用约定的协议,序列化和反序列化属于通信协议的一部分。通信协议每每采用分层模型,不一样模型每层的功能定义以及颗粒度不一样,例如:TCP/IP协议是一个四层协议,而OSI模型倒是七层协议模型。在OSI七层协议模型中展示层(Presentation Layer)的主要功能是把应用层的对象转换成一段连续的二进制串,或者反过来,把二进制串转换成应用层的对象--这两个功能就是序列化和反序列化。通常而言,TCP/IP协议的应用层对应与OSI七层协议模型的应用层,展现层和会话层,因此序列化协议属于TCP/IP协议应用层的一部分。本文对序列化协议的讲解主要基于OSI七层协议模型。数据库
相关厂商内容apache
相关赞助商编程
CNUTCon全球运维技术大会,9月10日-9月11日,上海·光大会展中心大酒店,精彩内容抢先看数组
不一样的计算机语言中,数据结构,对象以及二进制串的表示方式并不相同。安全
数据结构和对象:对于相似Java这种彻底面向对象的语言,工程师所操做的一切都是对象(Object),来自于类的实例化。在Java语言中最接近数据结构的概念,就是POJO(Plain Old Java Object)或者Javabean--那些只有setter/getter方法的类。而在C二进制串:序列化所生成的二进制串指的是存储在内存中的一块数据。C语言的字符串能够直接被传输层使用,由于其本质上就是以'0'结尾的存储在内存中的二进制串。在Java语言里面,二进制串的概念容易和String混淆。实际上String 是Java的一等公民,是一种特殊对象(Object)。对于跨语言间的通信,序列化后的数据固然不能是某种语言的特殊数据类型。二进制串在Java里面所指的是byte[],byte是Java的8中原生数据类型之一(Primitive data types)。网络
每种序列化协议都有优势和缺点,它们在设计之初有本身独特的应用场景。在系统设计的过程当中,须要考虑序列化需求的方方面面,综合对比各类序列化协议的特性,最终给出一个折衷的方案。数据结构
通用性有两个层面的意义。架构
如下两个方面的缘由会致使协议不够强健。
序列化和反序列化的数据正确性和业务正确性的调试每每须要很长的时间,良好的调试机制会大大提升开发效率。序列化后的二进制串每每不具有人眼可读性,为了验证序列化结果的正确性,写入方不得同时撰写反序列化程序,或提供一个查询平台--这比较费时;另外一方面,若是读取方未能成功实现反序列化,这将给问题查找带来了很大的挑战--难以定位是因为自身的反序列化程序的bug所致使仍是因为写入方序列化后的错误数据所致使。对于跨公司间的调试,因为如下缘由,问题会显得更严重。
若是序列化后的数据人眼可读,这将大大提升调试效率, XML和JSON就具备人眼可读的优势。
性能包括两个方面,时间复杂度和空间复杂度。
移动互联时代,业务系统需求的更新周期变得更快,新的需求不断涌现,而老的系统仍是须要继续维护。若是序列化协议具备良好的可扩展性,支持自动增长新的业务字段,而不影响老的服务,这将大大提供系统的灵活度。
在序列化选型的过程当中,安全性的考虑每每发生在跨局域网访问的场景。当通信发生在公司之间或者跨机房的时候,出于安全的考虑,对于跨局域网的访问每每被限制为基于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; } public class UserInfo { private Integer userid; private String name; private List<address> address; } </address>
XML是一种经常使用的序列化和反序列化协议,具备跨机器,跨语言等优势。 XML历史悠久,其1.0版本早在1998年就造成标准,并被普遍使用至今。XML的最初产生目标是对互联网文档(Document)进行标记,因此它的设计理念中就包含了对于人和机器都具有可读性。 可是,当这种标记文档的设计被用来序列化对象的时候,就显得冗长而复杂(Verbose 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 service。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)的事物上。
采用WSDL描述上述用户基本信息的例子以下:
<xsd:complexType name='Address'> <xsd:attribute name='city' type='xsd:string' /> <xsd:attribute name='postcode' type='xsd:string' /> <xsd:attribute name='street' type='xsd:string' /> </xsd:complexType> <xsd:complexType name='UserInfo'> <xsd:sequence> <xsd:element name='address' type='tns:Address'/> <xsd:element name='address1' type='tns:Address'/> </xsd:sequence> <xsd:attribute name='userid' type='xsd:int' /> <xsd:attribute name='name' type='xsd:string' /> </xsd:complexTyp>
SOAP协议具备普遍的群众基础,基于HTTP的传输协议使得其在穿越防火墙时具备良好安全特性,XML所具备的人眼可读(Human-readable)特性使得其具备出众的可调试性,互联网带宽的日益剧增也大大弥补了其空间开销大(Verbose)的缺点。对于在公司之间传输数据量相对小或者实时性要求相对低(例如秒级别)的服务是一个好的选择。因为XML的额外空间开销大,序列化以后的数据量剧增,对于数据量巨大序列持久化应用常景,这意味着巨大的内存和磁盘开销,不太适合XML。另外,XML的序列化和反序列化的空间和时间开销都比较大,对于对性能要求在ms级别的服务,不推荐使用。WSDL虽然具有了描述对象的能力,SOAP的S表明的也是simple,可是SOAP的使用绝对不简单。对于习惯于面向对象编程的用户,WSDL文件不直观。
JSON起源于弱类型语言Javascript, 它的产生来自于一种称之为"Associative array"的概念,其本质是就是采用"Attribute-value"的方式来描述对象。实际上在Javascript和PHP等弱类型语言中,类的描述方式就是Associative array。JSON的以下优势,使得它快速成为最普遍使用的序列化协议之一。
JSON实在是太简单了,或者说太像各类语言里面的类了,因此采用JSON进行序列化不须要IDL。这实在是太神奇了,存在一种自然的序列化协议,自身就实现了跨语言和跨平台。然而事实没有那么神奇,之因此产生这种假象,来自于两个缘由。
JSON在不少应用场景中能够替代XML,更简洁而且解析速度更快。典型应用场景包括:
如下是UserInfo序列化以后的一个例子:
{"userid":1,"name":"messi","address":[{"city":"北京","postcode":"1000000","street":"wangjingdonglu"}]}
Thrift是Facebook开源提供的一个高性能,轻量级RPC服务框架,其产生正是为了知足当前大数据量、分布式、跨语言、跨平台数据通信的需求。 可是,Thrift并不只仅是序列化协议,而是一个RPC框架。相对于JSON和XML而言,Thrift在空间开销和解析性能上有了比较大的提高,对于对性能要求比较高的分布式系统,它是一个优秀的RPC解决方案;可是因为Thrift的序列化被嵌入到Thrift框架里面,Thrift框架自己并无透出序列化和反序列化接口,这致使其很难和其余传输层协议共同使用(例如HTTP)。
对于需求为高性能,分布式的RPC服务,Thrift是一个优秀的解决方案。它支持众多语言和丰富的数据类型,并对于数据字段的增删具备较强的兼容性。因此很是适用于做为公司内部的面向服务构建(SOA)的标准RPC框架。
不过Thrift的文档相对比较缺少,目前使用的群众基础相对较少。另外因为其Server是基于自身的Socket服务,因此在跨防火墙访问时,安全是一个顾虑,因此在公司间进行通信时须要谨慎。 另外Thrift序列化以后的数据是Binary数组,不具备可读性,调试代码时相对困难。最后,因为Thrift的序列化和框架紧耦合,没法支持向持久层直接读写数据,因此不适合作数据持久化序列化协议。
struct Address { 1: required string city; 2: optional string postcode; 3: optional string street; } struct UserInfo { 1: required string userid; 2: required i32 name; 3: optional list<address> address; } </address>
Protobuf具有了优秀的序列化协议的所需的众多典型特征。
Protobuf是一个纯粹的展现层协议,能够和各类传输层协议一块儿使用;Protobuf的文档也很是完善。 可是因为Protobuf产生于Google,因此目前其仅仅支持Java、C#### 典型应用场景和非应用场景 Protobuf具备普遍的用户基础,空间开销小以及高解析性能是其亮点,很是适合于公司内部的对性能要求高的RPC调用。因为Protobuf提供了标准的IDL以及对应的编译器,其IDL文件是参与各方的很是强的业务约束,另外,Protobuf与传输层无关,采用HTTP具备良好的跨防火墙的访问属性,因此Protobuf也适用于公司间对性能要求比较高的场景。因为其解析性能高,序列化后数据量相对少,很是适合应用层对象的持久化场景。
它的主要问题在于其所支持的语言相对较少,另外因为没有绑定的标准底层传输层协议,在公司间进行传输层协议的调试工做相对麻烦。
message Address { required string city=1; optional string postcode=2; optional string street=3; } message UserInfo { required string userid=1; required string name=2; repeated Address address=3; }
Avro的产生解决了JSON的冗长和没有IDL的问题,Avro属于Apache Hadoop的一个子项目。 Avro提供两种序列化格式:JSON格式或者Binary格式。Binary格式在空间开销和解析性能方面能够和Protobuf媲美,JSON格式方便测试阶段的调试。 Avro支持的数据类型很是丰富,包括C#### 典型应用场景和非应用场景 Avro解析性能高而且序列化以后的数据很是简洁,比较适合于高性能的序列化服务。
因为Avro目前非JSON格式的IDL处于实验阶段,而JSON格式的IDL对于习惯于静态类型语言的工程师来讲不直观。
protocol Userservice { record Address { string city; string postcode; string street; } record UserInfo { string name; int userid; array<Address> address = []; } }
所对应的JSON Schema格式以下:
{ "protocol" : "Userservice", "namespace" : "org.apache.avro.ipc.specific", "version" : "1.0.5", "types" : [ { "type" : "record", "name" : "Address", "fields" : [ { "name" : "city", "type" : "string" }, { "name" : "postcode", "type" : "string" }, { "name" : "street", "type" : "string" } ] }, { "type" : "record", "name" : "UserInfo", "fields" : [ { "name" : "name", "type" : "string" }, { "name" : "userid", "type" : "int" }, { "name" : "address", "type" : { "type" : "array", "items" : "Address" }, "default" : [ ] } ] } ], "messages" : { } }
如下数据来自https://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking。
从上图可得出以下结论:
以上描述的五种序列化和反序列化协议都各自具备相应的特色,适用于不一样的场景。
感谢丁晓昀对本文的审校。