写点什么

APP 为什么用 JSON 协议与服务端交互:序列化相关知识

作者:程序员啊叶
  • 2022 年 7 月 29 日
  • 本文字数:5081 字

    阅读完需:约 17 分钟

APP为什么用JSON协议与服务端交互:序列化相关知识

在设计或重构系统的过程中,特别是在设计分布式、大数据量系统里面,序列化选型是一个重要环节,因为序列化协议影响系统的通用性、健壮性、安全性,是否易于调试、是否便于扩展。

序列化分理论和实践部分,理论部分描述只序列化是什么、要做什么,不关心实现(因为不考虑具体实现,所以自然不会考虑优缺点、应用场景);实践部分描述如何完成序列化操作,对象用什么方法表示,即把序列化从理论世界带进现实世界。序列化理论部分相当于是设计,实践部分相当于实现。

理论

序列化理论包含的三个方面

  1. 序列化定义;

  2. 序列化在通信协议中的地位;

  3. 序列化组件

1. 序列化问题是怎么产生的(序列化的定义)

互联网的发展产生了机器之间互相通讯的需求,机器之间互相通讯需要约定通讯协议,通信协议又要考虑数据如何表示、如何传输等问题。序列化就是通信协议里与数据的表示相关的那一部分协议。OSI 七层协议模型中的展现层(Presentation Layer)的主要功能是把对象转换成一段连续的二进制串,或把二进制串转换成对象–这就是序列化和反序列化。

所以,序列化和反序列化的定义就是:

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

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

注:不一定非要转换成二进制,只要是能传输到通信另一端都可以,例如 utf-8 字符串。

2. 序列化在通信协议中的地位

  1. OSI 七层协议模型,序列化位于展示层(Presentation Layer)

  2. 在 TCP/IP 协议中,序列化位于应用层。

3. 序列化和反序列化的组件

完整的序列化协议包含以下组件:

  1. IDL 文件(Interface description language)。参与通讯的各方需要对通讯的内容做相关约定。为了与语言和平台无关,这个约定需要采用与编程语言、平台无关的语言来进行描述。这种语言被称为接口描述语言(IDL),采用 IDL 撰写的协议约定称为 IDL 文件。

  2. IDL Compiler:IDL 文件中的约定需要一个编译器,将 IDL 文件转换成各编程语言的动态库。

  3. Stub/Skeleton Lib:负责序列化和反序列化工作的代码。Stub 是一段部署在客户端的代码,一方面接收应用层的参数,并对其序列化后通过底层协议栈发送到服务端;另一方面接收服务端序列化后的结果数据,反序列化后交给应用层;Skeleton 部署在服务端,其功能与 Stub 相反,从传输层接收序列化参数,反序列化后交给服务端应用层,并将应用层的执行结果序列化后最终传送给客户端。

序列化组件之间的交互关系:

序列化组件之间的交互

序列化组件的概念与数据库的相关概念类似:



实践

序列化协议的考虑点

  1. 支持哪些编程语言,能否跨语言

  2. 支持哪些平台,是否跨平台(例如支持哪些硬件架构、操作系统)

  3. 流行程度(序列化涉及通信双方,冷门的序列化协议需要的学习成本很高)

  4. 健壮性/鲁棒性

  5. 成熟程度(这个协议是否经历大量全面的测试、真实世界系统的检验,长期稳定运行)

  6. 是否简单易用

  7. 调试难度、可读性(序列化和反序列化的数据正确性和业务正确性的调试往往需要很长时间,良好的调试机制大大提高开发效率。序列化后的二进制串往往不具备人眼可读性,为了验证序列化结果的正确性,写入方不得不同时撰写反序列化程序,或提供一个查询平台–这比较费时;另一方面,如果读取方未能成功实现反序列化,难以确定是自身反序列化程序 bug 导致的还是写入方写入了错误序列化数据导致的。如果序列化后的数据人眼可读,这将大大提高调试效率, 例如 XML 和 JSON 就具有人眼可读的优点。)

  8. 空间性能(Verbosity),即序列化以后的数据所占用的空间大小。

  9. 时间性能(Complexity)。复杂的序列化协议会导致较长的解析时间,这可能使得序列化和反序列化成为系统瓶颈。

  10. 扩展性/兼容性。新增字段是否容易。业务系统需求的更新周期快,新需求不断涌现。如果序列化协议具有良好的可扩展性,支持自动增加新的业务字段,而不影响老的服务,这将大大提供系统的灵活度。

  11. 安全性/访问限制。例如阿里的 fastjson 经常有安全性问题。

  12. 文档。

已知的序列化协议

互联网早期序列化协议有 COM 和 CORBA。

COM 主要用于 Windows 平台,没有实现跨平台。COM 序列化利用了编译器中的虚表,使得学习成本很高。序列化得到的数据与编译器紧耦合,扩展属性非常麻烦。

CORBA 比较好的实现了跨平台,跨语言。COBRA 的主要问题是参与方过多,导致版本过多,版本之间兼容性差,使用复杂晦涩。早期设计的不成熟问题导致 COBRA 渐渐消亡。J2SE 1.3 之后的版本提供基于 CORBA 协议的 RMI-IIOP 技术,可以采用纯 Java 语言进行 CORBA 开发。

XML 序列化协议

XML 是一种语言,一种描述性的语言,最初目标是对文档进行编码,而且要求编码数据既能供人阅读也便于计算机处理。XML 在设计的时候就考虑到可读性,它还有跨机器、跨语言(这里指的是人类语言)等优点。XML 历史悠久,其 1.0 版本早在 1998 年就形成标准,并被广泛使用至今,所以 XML 是足够成熟的。既然 XML 能把有复杂结构的文档编码,那么 XML 也可以用于对象序列化,所以把 XML 列为一种序列化协议(例如.NET 框架和 gSOAP 框架就采用 XML 序列化)。但是 XML 用作序列化协议的时候,它就显得冗长复杂

XML 具有自我描述性,XML 自身就作为 IDL。XML 中的 IDL(即 XML 描述格式)有两种:DTD(Document Type Definition)和 XSD(XML Schema Definition)。XML 在某些编程语言里面具有非常简单易用的序列化 API,无需 IDL 文件和第三方 IDL 编译器(例如 Java XStream)。XML 被广泛应用在各种配置文件中,例如 O/R mapping、 Spring Bean Configuration File 等。

优点:跨平台、跨语言、成熟、可读性

缺点:复杂冗长(空间复杂度)


SOAP 序列化协议

SOAP(Simple Object Access protocol)是一种广泛应用的,基于 XML 的结构化消息传递协议,XML 被用于序列化和反序列化。SOAP 支持多种传输协议,但是最常用的还是 HTTP。SOAP 协议的 IDL 是 WSDL(Web Service Description Language)。

假如 java 应用层对象是:


用 WSDL 描述上述对象:


SOAP 具有安全、可扩展、跨语言、跨平台、支持多种传输协议,有广泛的群众基础,基于 HTTP 的传输协议使得 SOAP 在穿越防火墙时具有良好安全性,XML 的人眼可读特性使得其具有出众的可调试性,互联网带宽的发展逐渐弥补了其空间开销大的缺点。对于在公司之间传输少量数据或实时性要求相对低(例如秒级别),SOAP 是一个好的选择。

XML 空间开销大,数据量大、需要持久化应用场景不适合用 XML。XML 的序列化和反序列化的空间和时间开销都比较大,对于对性能要求达到 ms 级别的服务,不推荐 XML。WSDL 虽然具备对象描述能力,但是 SOAP 的使用不简单。对于习惯于面向对象编程的用户,WSDL 文件不直观。

JSON 序列化协议

JSON 起源于 JavaScript 中的”Associative array”的概念,本质就是采用”Attribute-value”方式描述对象。实际上在 Javascript 和 PHP 等弱类型语言中,类的描述方式就是 Associative array。JSON 有如下优点,使得它快速成为最广泛使用的序列化协议之一:

1、这种 Associative array 格式匹配工程师对对象的理解。

2、它也有 XML 的人眼可读(Human-readable)优点。

3、序列化后的数据简洁

4、JavaScript 先天支持,所以广泛应用于 Web browser 的应用常景,是 Ajax 的事实标准协议。

5、与 XML 相比,其协议比较简单,解析速度比较快。

6、松散的 Associative array 使得其具有良好的可扩展性和兼容性。

因为 json 其实是 associative array,与弱类型编程语言中的 class 在概念上对应,所以 JSON 序列化也不需要 IDL。原因:IDL 的目的是撰写 IDL 文件,而 IDL 文件被 IDL Compiler 编译后产生一些代码(Stub/Skeleton),而这些代码真正负责相应的序列化和反序列化工作。 但是由于 Associative array 和一般语言里面的 class 太相似,他们之间形成了一一对应关系,这就使得我们可以采用一套标准代码进行相应的转化。对于自身支持 Associative array 的弱类型语言,语言自身就具备操作 JSON 序列化后的数据的能力;对于 Java 这强类型语言,可以用反射解决。

JSON 在很多应用场景中可以替代 XML,更简洁且解析速度更快。典型应用场景包括:

  1. 公司之间传输数据量相对小,实时性要求相对低(例如秒级别)的服务。

  2. 基于 Web browser 的 Ajax 请求。

  3. 由于 JSON 具有非常强的前后兼容性,所以它适用于:接口经常发生变化,对可调式性要求高的场景,例如 Mobile app 与服务端的通讯(移动 APP 为什么要用 JSON 协议与服务端交互)。

  4. JSON 的典型应用场景是 JSON+HTTP,适合跨防火墙访问。

总体看,JSON 序列化的额外空间开销也比较大(但是比 XML 小多了),不适合数据量大或需要持久化的场景。没有统一的 IDL 降低了对参与方的约束,实际操作中往往只能采用文档方式来进行约定,这可能会给调试带来一些不便,延长开发周期。由于 JSON 在一些语言中的序列化和反序列化需要反射机制,所以性能要求 ms 级别的系统不建议使用。

Thrift 序列化协议

Thrift 是 Facebook 开发的一个 RPC 框架,满足了大数据量、分布式、跨语言、跨平台数据通讯的需求。Thrift 内部有一个自定义的序列化协议,即 Thrift 序列化协议。

优点:相对于 JSON 和 XML,Thrift 在空间开销和解析性能上有较大提升,适用于性能要求高的系统;它支持多种编程语言,数据类型丰富,对于数据字段的增删有较强的兼容性。

缺点:thrift 序列化被嵌入到 thrift 框架内部,然而 Thrift 框架没有对外提供 thrift 序列化和反序列化的接口,文档匮乏,用起来比较困难。Thrift 序列化之后得到的数据是 Binary 数组,不具有可读性,调试相对困难。Thrift 的序列化和框架紧耦合,无法支持向持久层直接读写数据,不适合用于数据持久化的场景。


Protobuf 序列化协议

Protobuf 具备序列化协议的众多优秀特征:

  1. 提供了标准的 IDL 和 IDL 编译器,对工程师非常友好。

  2. 序列化数据简洁紧凑,其序列化之后的数据量约为 XML 的 1/3 到 1/10。

  3. 解析速度非常快,比 XML 快约 20-100 倍。

  4. 提供了友好的动态库,使用非常简洁,反序列化只需要一行代码。

  5. Protobuf IDL 文件对于各个参与方业务产生了强力的约束

  6. Protobuf 与传输层无关,采用 HTTP,具有良好的跨防火墙访问属性。

Protobuf 是一个纯展示层协议,可以用于多种传输层协议;Protobuf 文档也非常完善。目前仅支持 Java、C++、Python 三种编程语言。另外 Protobuf 支持的数据类型较少,不支持常量。

所以 Protobuf 适用于跨公司、性能要求高的 RPC 调用应用场景。基于相同的原因,Protobuf 也适合用于对象的持久化。

缺点:支持语言较少,没有绑定标准传输层协议,跨公司进行传输层协议调试相对麻烦。

Google gPRC 框架已采用 Protobuf 序列化协议,可以参考。


Hessian 序列化协议

hessian 也是一种常见的序列化协议,经常在 RPC 框架中使用,例如 Dubbo、Pigeon 等框架都支持 Hessian,甚至经常被 RPC 框架用作默认序列化方案。

Avro 序列化协议

Avro 是 Apache Hadoop 的子项目,解决了 JSON 的冗长和没有 IDL 的问题。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 解析性能高且序列化数据简洁,适合高性能序列化服务。


序列化协议 Benchmark

解析性能

序列化之空间开销

1、XML 序列化(Xstream)无论在性能和简洁性上都比较差。

2、Thrift 与 Protobuf 相比在时空开销方面都有一定劣势。

3、Protobuf 和 Avro 在两方面表现都非常优越。

序列化协议的选型

以上几种序列化协议各自具有特点,适用于不同场景:

1、公司之间系统调用,性能要求 100ms,基于 XML 的 SOAP 协议是一个值得考虑的方案。

2、基于 Web browser 的 Ajax,以及移动 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 可以优先考虑。

用户头像

还未添加个人签名 2022.07.13 加入

还未添加个人简介

评论

发布
暂无评论
APP为什么用JSON协议与服务端交互:序列化相关知识_Java_程序员啊叶_InfoQ写作社区