序列化和反序列化-刘丁

#一、定义以及相关概念

互联网的产生带来了机器间通讯的需求,而互联通讯的双方需要采用约定的协议,序列化和反序列化属于通讯协议的一部分。通讯协议往往采用分层模型,不同模型每层的功能定义以及颗粒度不同,例如: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

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

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

序列化组件 数据库组件 说明
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


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://tech.meituan.com/serialization_vs_deserialization.html?utm_source=tuicool?utm_source=tuicool

通信协议之序列化 - 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=

时间: 2024-10-28 19:38:25

序列化和反序列化-刘丁的相关文章

序列化和反序列化1

本文来自:http://kb.cnblogs.com/page/515982/ 作者: 刘丁  来源: 美团技术团队  发布时间: 2015-04-23 17:01  阅读: 1541 次  推荐: 3   原文链接   [收藏] 摘要 序列化和反序列化几乎是工程师们每天都要面对的事情,但是要精确掌握这两个概念并不容易:一方面,它们往往作为框架的一部分出现而湮没在框架之中:另一方面,它们会以其他更容易理解的概念出现,例如加密.持久化.然而,序列化和反序列化的选型却是系统设计或重构一个重要的环节,

C#序列化与反序列化学习

最近为了换一份新工作,准备了不少笔试题.从笔试当中自己发现了不少基础知识的盲点.很庆幸这样的机会,可以让自己对于基础知识的理解又上升一个台阶.此文介绍C#里面的序列化与反序列化的知识,如果你是大鸟,请口下留情. 首先,什么是序列化与反序列化呢? 序列化就是将对象的状态信息转换为可以存储或传输形式的过程.其实就是将对象持久化,比如说把对象保存为二进制或者是XML的方式.可以将对象序列到流.磁盘.内存和网络等等.相反,反序列化则是将存储或传输形式转换为对象的过程. 那么,运用序列化的好处又是什么呢?

二叉树的序列化和反序列化

http://blog.csdn.net/qq_27703417/article/details/70958692 先序遍历二叉树,如果遇到空节点,就在str的末尾加上"#!","#"表示这个节点为空,节点值不存在,当然你也可以用其他的特殊字符,"!"表示一个值的结束.如果遇到不为空的节点,假设节点值为3,就在str的末尾加上"3!".现在请你实现树的先序序列化. 先序遍历 import java.util.*; //使用递归

PHP中的抽象类与抽象方法/静态属性和静态方法/PHP中的单利模式(单态模式)/串行化与反串行化(序列化与反序列化)/约束类型/魔术方法小结

  前  言  OOP  学习了好久的PHP,今天来总结一下PHP中的抽象类与抽象方法/静态属性和静态方法/PHP中的单利模式(单态模式)/串行化与反串行化(序列化与反序列化). 1  PHP中的抽象类与抽象方法 1.什么是抽象方法?              没有方法体 {} 的方法,必须使用abstract 关键字修饰.这样的方,我们叫做抽象方法.                    abstract function say(); //    抽象方法 2.什么是抽象类?        

序列化与反序列化

对象的序列化,反序列化 1)对象序列化,就是将Object转化为byte序列,反之叫对象的反序列化 2)序列化流(ObjectOutputStream),是过滤流----writeObject() 反序列化流(ObjectInputStream)------readObject() 3)序列化接口(Serializable) 对象必须实现序列化接口,才能进行序列化,否则将出现异常 这个接口,没有任何方法,只是一个标准

java序列化与反序列化

public interface Serializable类通过实现 java.io.Serializable 接口以启用其序列化功能.未实现此接口的类将无法使其任何状态序列化或反序列化.可序列化类的所有子类型本身都是可序列化的.序列化接口没有方法或字段,仅用于标识可序列化的语义. 序列化就是将一个对象的状态(各个属性量)保存起来,然后在适当的时候再获得.         序列化分为两大部分:序列化和反序列化.序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输.反序列

序列化与反序列化总结(Serializable和Parcelable)

序列化是指将对象的状态信息转换为可以存储或传输的形式的过程. 在Java中创建的对象,只要没有被回收就可以被复用,但是,创建的这些对象都是存在于JVM的堆内存中,JVM处于运行状态时候,这些对象可以复用, 但是一旦JVM停止,这些对象的状态也就丢失了. 在实际生活中,需要将对象持久化,需要的时候再重新读取出来,通过对象序列化,可以将对象的状态保存为字节数组,需要的时候再将字节数组反序列化为对象. 对象序列化可以很容易的在JVM中的活动对象和字节数组(流)之间转换,广泛用于RMI(远程方法调用)以

Java的序列化与反序列化

Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java序列化与反序列化?本文围绕这些问题进行了探讨. 1.Java序列化与反序列化 Java序列化是指把Java对象转换为字节序列的过程:而Java反序列化是指把字节序列恢复为Java对象的过程. 2.为什么需要序列化与反序列化 我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本.图片.音频.视频等, 而这些数据都会以二进制序列的形式在网络上传送.那么当两个Java进程进行通信时,能否实现进程间的对象传送

Python Special Syntax 8: 序列化与反序列化--&gt;华丽丽的叫 pickle(泡菜?!)

直接上代码吧 #-*-coding:utf-8 import os if os.path.exists('d:\\cpickle.data'): os.remove('d:\\cpickle.data') import cPickle as P shoplist=['apple','banana','pear'] P.dump(shoplist,file('d:\\cpickle.data','w')) f=file('d:\\cpickle.data') while True: content