从分布式到微服务:深入理解网络,网络传输中的对象序列化问题

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,182元/月
简介: 仅仅懂了Socket编程还不够,因为我们不是简单地写一个发送字符串的Hello World程序,需要实现复杂的对象实例传输,因此,如何将一个对象实例编码成为高效的二进制数据报文传输到对端,并且正确地“还原”出来,就是一个专业的技术问题了。

网络传输中的对象序列化问题

仅仅懂了Socket编程还不够,因为我们不是简单地写一个发送字符串的Hello World程序,需要实现复杂的对象实例传输,因此,如何将一个对象实例编码成为高效的二进制数据报文传输到对端,并且正确地“还原”出来,就是一个专业的技术问题了。

对象序列化技术是Java本身的重要底层机制之一,因为Java一开始就是面向网络的,远程方法调用(RPC)是必不可少的,需要方便地将一个对象实例通过网络传输到远端。Java自身的序列化机制有两个大问题。

  • 序列化的数据比较大,传输效率低。
  • 其他语言无法识别和对接。

在后来相当长的一段时间内,基于XML格式编码的对象序列化机制盛行,它解决了多语言兼容的问题,同时比二进制的序列化方式更容易理解和排错,于是基于XML 的SOAP和其上的 Web Service框架几乎成为各个主流开发语言的必备扩展包,会不会熟练定义和开发WebService接口,一度成为一个“高级技能”。后来,基于JSON的简单文本格式编码的HTTP REST接口又基本取代了复杂的Web Service接口,成为事实上的分布式架构中远程通信的首要选择。

JSON序列化存在占用空间大、性能低下等缺陷,随着多语言协作开发的互联网应用越来越普及,更多的移动客户端应用需要更高效地传输数据,以提升用户体验。在这种情况下,与语言无关的高效二进制编码协议就成为热点技术之一。

首先,诞生了一个知名开源二进制序列化框架——MessagePack,它的出现比 Google 的Protocol Buffers要早,是模仿JSON 设计的一个高性能二进制的通用序列化框架,它有两大优势:序列化后空间占用最小,而且更快。如下所示是它的序列化机制原理示意图。

网络异常,图片无法展示
|

我们看到,在 MessagePack 中,数据类型被分为两大类:定长数据(整数、浮点数、布尔、空值等)与变长数据(字节数组、通用数组、集合类型的数据)。对于定长数据,只要在序列化时标明数据类型与对应的值即可;对于变长数据,则多了一个“长度”属性,用来表明数据的真实长度。下图是其数据类型(Type)的分类详情,我们看到,为了最大可能地节省存储空间,MessagePack把数值型又细分了很多种,不仅如此,连正数和负数都分开了。

对照上面的数据类型定义,我们就可以理解下图中一个有27个字节的JSON的 Map是如何在 MessagePack 里用18个字节序列化的。

网络异常,图片无法展示
|

其次,除了MessagePack,Google开源的多语言支持的Protocol Buffers编码协议也是这方面的代表作品,其官方实现了C++、Python、Java三种语言的API接口,其他语言版本也相继由不同的作者实现。据统计,截至2010年,采用 Protocol Buffers定义的报文格式就接近5万种,这些报文格式被大量用于RPC 调用与持久化数据传输和存储系统中。

如果要做到语言中立及多语言支持,就不能用任何一种已有语言的语法来定义协议,只能用一种新的“中立”的第三方语言来描述协议。这种指导思想早在20世纪90年代的 COBRA里就体现过了。当时,为了定义各种语言都能使用的RPC接口,COBRA设计了IDL接口定义文档及语言相关的编译器,将接口语言编译成相应语言定义的接口,并且配套复杂的多语言支持的数据序列化机制,从而描绘了一个大一统的、从未真正实现的“完美IT世界”。ProtocolBuffers同样创建了一个后缀名为.proto 的描述文件,用来定义一个数据对象的具体结构。下面是一个简单的例子:

网络异常,图片无法展示
|

在这个例子中定义了一个被称为 helloworld的数据对象,也被称为“消息”它有3个属性:一个32位整数的id、一个字符串类型的str变量,是必须赋值的属性;一个可选的32位整数变量 opt。在定义好 proto文件后,就可以将其编译成支持各种语言的接口代码了。如果有兴趣,则建议将其编译成自己熟悉的语言,分析隐藏在其背后的复杂的编码和解码细节,这有助于你加深对RPC 实现机制的理解,因为高性能RPC的关键技术点之一就在于如何设计和实现一个高效的数据序列化机制。这里提示一点,与MessagePack类似,Protocol Buffers为了减少序列化后的存储空间,也使用了一些技巧,比如Varint是Protocol Buffers中的变长整数类型,用一个或多个字节来表示一个数字,数字的值越小,使用的字节数存储越少,可减少用来表示数字的字节。比如对于 int32类型的数字,一般需要4个字节来表示。但是采用Varint后,对于很小的 int32类型的数字,则可以用1个字节来表示。

在 Protocol Buffers之后,Google又开源了一个新项目——Google FlatBuffers,在性能、序列化过程中内存占用的大小、第三方依赖库的数量、编译后生成的中间代码数量等方面都做了大幅改进。随后Cap'n公司发布声明,称Google这个 FlatBuffers 的设计实现很像该公司的Cap'nProto,并且给出 Cap'n Proto和 Protocol Buffers 的性能对比图,来证明Google FlatBuffers 的确做了很多改变。

网络异常,图片无法展示
|

接下来说说Apache Avro(后简称Avro),它是一个开源项目,主要使用Java实现,支持多语言。它完全针对Protocol Buffers而来,是一种新的设计思路,其作者说:“这个世界上的每个问题都有几种解决思路”。

Avro原本是Hadoop中的一个子项目,用于实现RPC调用, Hadoop的其他项目中例如HBase和Hive的 Client端与服务端的数据传输也采用了这个项目。Avro 与Protocol Buffers 的最大区别在于,它采用了预先定义的Schema(模式)来描述对象的序列化结构,从而无须编译。在使用Avro时必须先确定Schema,而Schema类似于表结构的定义,正是模式的引入,使得数据具有了自描述的功能,同时能够实现动态加载。另外,与其他数据序列化系统如Protocol Buffers相比,在数据之间不存在其他任何标识,有利于提高数据处理效率。

Avro的模式采用JSON来描述,下面的代码定义了一个名为User 的对象及其属性:

网络异常,图片无法展示
|

Avro独有的Schema模式的设计,以及无须编译生成中间代码的做法,大大简化和加速了各种格式数据传输的开发联调工作。2014年,微软也发布了自己对Avro通信协议的实现,即.NET版本的语言实现,截至2020年2月,Avro已经有了C、C++、C#、Java、PHP、Python与Ruby等几个主流编程语言的实现版本。

本节最后讨论一下 RPC中的数据序列化可能带来的风险问题,这个问题在Java RMI中比较明显,因为JavaRMI采用了Java对象序列化机制在网络中传输数据。Java序列化就是把Java对象转换成字节流,以便将其保存在内存、文件、数据库中;反序列化是Java序列化对应的逆过程,即将字节流还原成对象本身。这看起来没什么问题,但是如果某个应用可以让用户输入一些数据,并且将这些输入的数据作为某个Java对象的属性通过Java序列化机制传输到服务器端,则攻击者可以通过构造“恶意输入”,让服务器端对应的反序列化程序产生“非预期的对象”,而这些非预期的对象有可能导致恶意代码的执行,与经典的SQL注入这样的安全漏洞在本质上是一样的。

对象序列化的安全漏洞问题并非Java所特有的,在PHP和 Python中也有类似的问题。Java序列化安全问题的根源在于,ObjectInputStream在反序列化时没有对生成的对象的类型做限制!直到JDK 9才增加了一个filter机制来解决这个问题,后面才打补丁到JDK 6、7、8的特定版本上!反观ZeroC lce、Thrift、ProtoBuf等传统RPC,它们通过IDL生成代码,并在IDL中严格控制数据类型,因而是安全的。这又印证了一个道理:代码实现越复杂,Bug越多!

本文给大家讲解的内容是架构解密从分布式到微服务:深入理解网络,网络传输中的对象序列化问题

本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

相关文章
|
缓存
银河麒麟server-V10配置镜像源
银河麒麟server-V10配置镜像源
15836 1
|
Arthas SQL Java
Arthas之WatchSql
在使用Arthas排查线上问题的时候,有些时候我们需要查看某些Sql的生成,如果线上没有完备的APM的话,那么如何临时查看呢,前几篇文章我们分析了Mybatis的插件机制,如果你还记得的话,我们可以通过watch这个插件进行查看。
2852 1
Arthas之WatchSql
|
缓存 Perl
如何修改openeuler为阿里源
修改openeuler为阿里源
6010 0
|
5月前
|
人工智能 IDE 程序员
阿里也出手了!灵码AI IDE问世
各位程序员小伙伴们,是不是还在为写代码头秃?别担心,阿里云带着它的通义灵码 AI IDE 来拯救你啦!
2465 3
|
机器学习/深度学习 TensorFlow 算法框架/工具
深度学习中的正则化技术及其对模型性能的影响
【8月更文挑战第26天】本文将深入探讨深度学习领域中的正则化技术,并分析其如何塑造模型性能。我们将从理论出发,逐步引导读者理解不同正则化方法背后的原理,并通过实例展示它们在实际问题中的应用效果。文章旨在启发读者思考如何在特定的深度学习任务中选择合适的正则化策略,以优化模型的表现。
|
弹性计算 安全 容灾
【深度好文】为什么说用好VPC很重要!
本文详细探讨了阿里云VPC(Virtual Private Cloud)的使用方法及其重要性。 VPC作为用户云上的“数据中心”,提供了安全隔离的网络环境,帮助用户构建和管理云服务。文章首先对比了经典网络和VPC的区别,强调了VPC在安全性、灵活性和扩展性方面的优势。接着,通过具体的规划步骤,包括选择地域、账号规划、网段规划、安全隔离设计等,展示了如何有效利用VPC。此外,还介绍了VPC连接互联网的方式及安全措施,以及VPC与IDC互访的解决方案。 总体而言,VPC不仅是用户上云的第一步,更是构建稳定、高效云基础设施的关键。
|
12月前
|
搜索推荐 关系型数据库 MySQL
MySQL中的模糊匹配技巧:无需ES的高效实现
在数据库应用中,模糊匹配是一个常见的需求,尤其在处理搜索功能时。虽然Elasticsearch(ES)等搜索引擎在处理文本搜索方面表现出色,但在一些场景下,直接使用MySQL数据库实现模糊匹配也是一个经济且高效的选择。本文将分享如何在不引入ES的情况下,利用MySQL实现模糊匹配的五大步骤和十个实战案例。
868 1
|
存储 程序员 编译器
简述 C、C++程序编译的内存分配情况
在C和C++程序编译过程中,内存被划分为几个区域进行分配:代码区存储常量和执行指令;全局/静态变量区存放全局变量及静态变量;栈区管理函数参数、局部变量等;堆区则用于动态分配内存,由程序员控制释放,共同支撑着程序运行时的数据存储与处理需求。
593 22
|
11月前
|
机器学习/深度学习 新零售 人工智能
基于阿里云AI购物助手解决方案的深度评测
阿里云推出的AI购物助手解决方案,采用模块化架构,涵盖智能对话引擎、商品知识图谱和个性化推荐引擎。评测显示其在智能咨询问答、个性化推荐和多模态交互方面表现出色,准确率高且响应迅速。改进建议包括提升复杂问题理解、简化推荐过程及优化话术。总体评价认为该方案技术先进,应用效果好,能显著提升电商购物体验并降低运营成本。
1051 0
|
XML Java 数据库连接
解决在mybatis中出现的org.apache.ibatis.exceptions.PersistenceException~
解决在mybatis中出现的org.apache.ibatis.exceptions.PersistenceException~
2185 0

热门文章

最新文章