序列化和反序列化-刘丁

Posted lord_is_layuping

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了序列化和反序列化-刘丁相关的知识,希望对你有一定的参考价值。

 

#一、定义以及相关概念

互联网的产生带来了机器间通讯的需求,而互联通讯的双方需要采用约定的协议,序列化和反序列化属于通讯协议的一部分。通讯协议往往采用分层模型,不同模型每层的功能定义以及颗粒度不同,例如:TCP/IP协议是一个四层协议,而OSI模型却是七层协议模型。OSI七层协议模型中展现层(Presentation Layer)的主要功能是把应用层对象转换成一段连续的二进制串,或者反过来,把二进制串转换成应用层对象--这两个功能就是序列化和反序列化。

一般而言,TCP/IP协议应用层对应与OSI七层协议模型的应用层,展示层和会话层,所以序列化协议属于TCP/IP协议应用层的一部分。

本文对序列化协议的讲解主要基于OSI七层协议模型

  • 序列化: 将数据结构对象转换成二进制串的过程

  • 反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程

数据结构对象二进制串

不同的计算机语言中,数据结构对象以及二进制串的表示方式并不相同。

数据结构对象:对于类似Java这种完全面向对象的语言,工程师所操作的一切都是对象(Object),来自于类的实例化。在Java语言中最接近数据结构的概念,就是POJO(Plain Old Java Object)或者Javabean--那些只有setter/getter方法的类。而在C++这种半面向对象的语言中,数据结构struct对应,对象和class对应。

二进制串:序列化所生成的二进制串指的是存储在内存中的一块数据。C++语言具有内存操作符,所以二进制串的概念容易理解,例如,C++语言的字符串可以直接被传输层使用,因为其本质上就是以\'\\0\'结尾的存储在内存中的二进制串。在Java语言里面,二进制串的概念容易和String混淆。实际上String 是Java的一等公民,是一种特殊对象(Object)。对于跨语言间的通讯,序列化后的数据当然不能是某种语言的特殊数据类型。二进制串在Java里面所指的是byte[],byte是Java的8中原生数据类型之一(Primitive data types)。

#二、序列化协议特性

略 见 序列化和反序列化 - - https://tech.meituan.com/serialization_vs_deserialization.html?utm_source=tuicool?utm_source=tuicool

#三、序列化和反序列化的组件

序列化组件

接口描述语言(IDL

IDL语言全称是Interactive Data Language,对于老一些的计算机专家来说,这个缩写还有另外几个意思,就是Interface description language,接口描述语言,是CORBA规范的一部分。在搜索资料的时候,要适当加以确认。

IDL(Interface Description Language),它是一种描述语言,也是一个中间语言,IDL一个使命就是规范和约束,就像前面提到,规范使用类型,提供跨语言特性。通过工具分析idl文件,生成各种语言代码


Gencpp.exe sample.idl 输出 sample.cpp sample.h


Genphp.exe sample.idl 输出 sample.php


Genjava.exe sample.idl 输出 sample.java


序列化组件与数据库访问组件的对比


数据库访问对于很多工程师来说相对熟悉,所用到的组件也相对容易理解。下表类比了序列化过程中用到的部分组件和数据库访问组件的对应关系,以便于大家更好的把握序列化相关组件的概念。

序列化组件数据库组件说明
IDLDDL用于建表或者模型的语言
DL fileDB Schema表创建文件或模型文件
Stub/Skeleton libO/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


XML&SOAP

典型应用场景和非应用场景

SOAP协议具有广泛的群众基础,基于HTTP的传输协议使得其在穿越防火墙时具有良好安全特性,XML所具有的人眼可读(Human-readable)特性使得其具有出众的可调试性,互联网带宽的日益剧增也大大弥补了其空间开销大(Verbose)的缺点。对于在公司之间传输数据量相对小或者实时性要求相对低(例如秒级别)的服务是一个好的选择。

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

IDL文件举例

采用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:complexType>

JSON(javascript Object Notation)

典型应用场景和非应用场景

JSON在很多应用场景中可以替代XML,更简洁并且解析速度更快。典型应用场景包括:
1、公司之间传输数据量相对小,实时性要求相对低(例如秒级别)的服务。
2、基于Web browser的Ajax请求
3、由于JSON具有非常强的前后兼容性,对于接口经常发生变化,并对可调式性要求高的场景,例如Mobile app与服务端的通讯
4、由于JSON的典型应用场景是JSON+HTTP,适合跨防火墙访问

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

IDL文件举例

以下是UserInfo序列化之后的一个例子:

{"userid":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 UserInfo{ 
    1: required string userid;
    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支持的数据类型相对较少,不支持常量类型。由于其设计的理念是纯粹的展现层协议(Presentation 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 UserInfo{
    required string userid=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解析性能高并且序列化之后的数据非常简洁,比较适合于高性能的序列化服务

由于Avro目前非JSON格式的IDL处于实验阶段,而JSON格式的IDL对于习惯于静态类型语言的工程师来说不直观。

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" : { }}

#五、Benchmark以及选型建议

##Benchmark
以下数据来自https://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking

解析性能

解析性能

序列化之空间开销

序列化大小

从上图可得出如下结论:
1、XML序列化(Xstream)无论在性能和简洁性上比较差。
2、Thrift与Protobuf相比在时空开销方面都有一定的劣势。
3、Protobuf和Avro在两方面表现都非常优越。

选型建议

以上描述的五种序列化和反序列化协议都各自具有相应的特点,适用于不同的场景:
1、对于公司间的系统调用,如果性能要求在100ms以上的服务,基于XML的SOAP协议是一个值得考虑的方案。
2、基于Web browser的Ajax,以及Mobile app与服务端之间的通讯,JSON协议是首选。对于性能要求不太高,或者以动态类型语言为主,或者传输数据载荷很小的的运用场景,JSON也是非常不错的选择。
3、对于调试环境比较恶劣的场景,采用JSON或XML能够极大的提高调试效率,降低系统开发成本。
4、当对性能和简洁性有极高要求的场景,Protobuf,Thrift,Avro之间具有一定的竞争关系。
5、对于T级别的数据的持久化应用场景,Protobuf和Avro是首要选择。如果持久化后的数据存储在Hadoop子项目里,Avro会是更好的选择。
6、由于Avro的设计理念偏向于动态类型语言,对于动态语言为主的应用场景,Avro是更好的选择。
7、对于持久层非Hadoop项目,以静态类型语言为主的应用场景,Protobuf会更符合静态类型语言工程师的开发习惯。
8、如果需要提供一个完整的RPC解决方案,Thrift是一个好的选择。
9、如果序列化之后需要支持不同的传输层协议,或者需要跨防火墙访问的高性能场景,Protobuf可以优先考虑。

参考文献:

http://www.codeproject.com/Articles/604720/JSON-vs-XML-Some-hard-numbers-about-verbosity
https://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking
http://en.wikipedia.org/wiki/Serialization
http://en.wikipedia.org/wiki/Soap
http://en.wikipedia.org/wiki/XML
http://en.wikipedia.org/wiki/JSON
http://avro.apache.org/
http://www.oracle.com/technetwork/java/rmi-iiop-139743.html

参考:

通信协议之序列化 - https://mp.weixin.qq.com/s?src=3&timestamp=1499594507&ver=1&signature=kIPbRVrJCHVJGQ9JKVjVN68SPBrvMgbQHFUQI4tNlT9zNV4b0WLnbo5nngOVx2xm0JcILFEM1XzfX7XFognvlijLppizp0lD4M*NiwoEG9cIEx1ozj0Jz0u2zcw65nyEU7r1Btwg8AuMlqSckFBBzTW0Oc4MTMhG6Vz2Al0Ea1E=

小林教IDL【§1. IDL简介 - https://mp.weixin.qq.com/s?src=3&timestamp=1499594507&ver=1&signature=dJFkS3xbR1f05G1tyzVZAjZPlFLta1now9tRWp7IO-9pRzKN2EpMWO61Uv9hJUuanXwdBmxtJsZ0qbZcdv*kWwZrduYtmLxbdGKXHm9fsdm8XYSnt6L4ItWF-h*uxARrIb3pUfdK3g6T9gOr5Y9xHKSQXMHgCQoBSwt4W5zSZ7E=






以上是关于序列化和反序列化-刘丁的主要内容,如果未能解决你的问题,请参考以下文章

序列化和反序列化代码

python代码实现二叉树的序列化和反序列化

序列化和反序列化的示例代码

Django REST framework序列化

rest_framework

csharp 此代码演示了json序列化和反序列化的4种方法