【RPC】序列化:对象怎么在网络中传输?

今天来聊下RPC框架中的序列化。在不同的场景下合理地选择序列化方式,对提升RPC框架整体的稳定性和性能是至关重要的。

一、为什么需要序列化?

首先,我们得知道什么是序列化与反序列化。

网络传输的数据必须是二进制数据,但调用方请求的出入参数都是对象。对象是不能直接在网络中传输的,所以我们需要提前把它转成可传输的二进制,并且要求转换算法是可逆的,这个过程我们一般叫做“序列化”。 这时,服务提供方就可以正确地从二进制数据中分割出不同的请求,同时根据请求类型和序列化类型,把二进制的消息体逆向还原成请求对象,这个过程我们称之为“反序列化”。

这两个过程如下图所示:

总结来说,序列化就是将对象转换成二进制数据的过程,而反序列就是反过来将二进制转换为对象的过程。

那么RPC框架为什么需要序列化呢?来看下下RPC的通信流程:

因为网络传输的数据必须是二进制数据,所以在RPC调用中,对入参对象与返回值对象进行序列化与反序列化是一个必须的过程。

二、有哪些常用的序列化?

那这么看来,你会不会觉得这个过程很简单呢?实则不然,很复杂。我们可以先看看都有哪些常用的序列化,下面我来简单地介绍下几种常用的序列化方式。

2.1、JDK原生序列化

如果你会使用Java语言开发,那么你一定知道JDK原生的序列化,下面是JDK序列化的一个例子:

public class Student implements Serializable { 
    //学号
    private int no;
    //姓名
    private String name;
    public int getNo() {
        return no;
    }
    public void setNo(int no) {
        this.no = no;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }@
    Overridepublic String toString() {
        return "Student{" + "no="
        no + ", name='"
        name + '\'' + '}';
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        String home = System.getProperty("user.home");
        String basePath = home + "/Desktop";
        FileOutputStream fos = new FileOutputStream(basePath + "student.dat");
        Student student = new Student();
        student.setNo(100);
        student.setName("TEST_STUDENT");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(student);
        oos.flush();
        oos.close();
        FileInputStream fis = new FileInputStream(basePath + "student.dat");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Student deStudent = (Student) ois.readObject();
        ois.close();
        System.out.println(deStudent);
    }
}

我们可以看到,JDK自带的序列化机制对使用者而言是非常简单的。序列化具体的实现是由ObjectOutputStream完成的,而反序列化的具体实现是由ObjectInputStream完成的。

那么JDK的序列化过程是怎样完成的呢?我们看下下面这张图:

序列化过程就是在读取对象数据的时候,不断加入一些特殊分隔符,这些特殊分隔符用于在反序列化过程中截断用。

  • 头部数据用来声明序列化协议、序列化版本,用于高低版本向后兼容

  • 对象数据主要包括类名、签名、属性名、属性类型及属性值,当然还有开头结尾等数据,除了属性值属于真正的对象值,其他都是为了反序列化用的元数据

  • 存在对象引用、继承的情况下,就是递归遍历“写对象”逻辑

实际上任何一种序列化框架,核心思想就是设计一种序列化协议,将对象的类型、属性类型、属性值一一按照固定的格式写到二进制字节流中来完成序列化,再按照固定的格式一一读出对象的类型、属性类型、属性值,通过这些信息重新创建出一个新的对象,来完成反序列化。

2.2、JSON

JSON可能是我们最熟悉的一种序列化格式了,JSON是典型的Key-Value方式,没有数据类型,是一种文本型序列化框架,JSON的具体格式和特性,网上相关的资料非常多,这里就不再介绍了。

他在应用上还是很广泛的,无论是前台Web用Ajax调用、用磁盘存储文本类型的数据,还是基于HTTP协议的RPC框架通信,都会选择JSON格式。

但用JSON进行序列化有这样两个问题,你需要格外注意:

  • JSON进行序列化的额外空间开销比较大,对于大数据量服务这意味着需要巨大的内存和磁盘开销;

  • JSON没有类型,但像Java这种强类型语言,需要通过反射统一解决,所以性能不会太好。

所以如果RPC框架选用JSON序列化,服务提供者与服务调用者之间传输的数据量要相对较小,否则将严重影响性能。

2.3、Hessian

Hessian是动态类型、二进制、紧凑的,并且可跨语言移植的一种序列化框架。Hessian协议要比JDK、JSON更加紧凑,性能上要比JDK、JSON序列化高效很多,而且生成的字节数也更小。

使用代码示例如下:

Student student = new Student();
student.setNo(101);
student.setName("HESSIAN");

//把student对象转化为byte数组
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(bos);
output.writeObject(student);
output.flushBuffer();
byte[] data = bos.toByteArray();
bos.close();

//把刚才序列化出来的byte数组转化为student对象
ByteArrayInputStream bis = new ByteArrayInputStream(data);
Hessian2Input input = new Hessian2Input(bis);
Student deStudent = (Student) input.readObject();
input.close();
System.out.println(deStudent);

相对于JDK、JSON,由于Hessian更加高效,生成的字节数更小,有非常好的兼容性和稳定性,所以Hessian更加适合作为RPC框架远程通信的序列化协议。

但Hessian本身也有问题,官方版本对Java里面一些常见对象的类型不支持,比如:

  • Linked系列,LinkedHashMap、LinkedHashSet等,但是可以通过扩展CollectionDeserializer类修复;

  • Locale类,可以通过扩展ContextSerializerFactory类修复;

  • Byte/Short反序列化的时候变成Integer。

以上这些情况,你在实践时需要格外注意。

2.4、Protobuf

Protobuf 是 Google 公司内部的混合语言数据标准,是一种轻便、高效的结构化数据存储格式,可以用于结构化数据序列化,支持Java、Python、C++、Go等语言。Protobuf使用的时候需要定义IDL(Interface description language),然后使用不同语言的IDL编译器,生成序列化工具类,它的优点是:

  • 序列化后体积相比 JSON、Hessian小很多;

  • IDL能清晰地描述语义,所以足以帮助并保证应用程序之间的类型不会丢失,无需类似 XML 解析器;

  • 序列化反序列化速度很快,不需要通过反射获取类型;

  • 消息格式升级和兼容性不错,可以做到向后兼容。

Protobuf 非常高效,但是对于具有反射和动态能力的语言来说,这样用起来很费劲,这一点就不如Hessian,比如用Java的话,这个预编译过程不是必须的,可以考虑使用Protostuff。

Protostuff不需要依赖IDL文件,可以直接对Java领域对象进行反/序列化操作,在效率上跟Protobuf差不多,生成的二进制格式和Protobuf是完全相同的,可以说是一个Java版本的Protobuf序列化框架。但在使用过程中,我遇到过一些不支持的情况:

  • 不支持null;

  • ProtoStuff不支持单纯的Map、List集合对象,需要包在对象里面。

三、RPC框架中如何选择序列化?

刚刚简单地介绍了几种最常见的序列化协议,其实远不止这几种,还有 Message pack、kryo等。那么面对这么多的序列化协议,在RPC框架中我们该如何选择呢?

首先你可能想到的是性能和效率,不错,这的确是一个非常值得参考的因素。我刚才讲过,序列化与反序列化过程是RPC调用的一个必须过程,那么序列化与反序列化的性能和效率势必将直接关系到RPC框架整体的性能和效率。

那除了这点,你还想到了什么?

对,还有空间开销,也就是序列化之后的二进制数据的体积大小。序列化后的字节数据体积越小,网络传输的数据量就越小,传输数据的速度也就越快,由于RPC是远程调用,那么网络传输的速度将直接关系到请求响应的耗时。

还有什么因素可以影响到我们的选择?

没错,就是序列化协议的通用性和兼容性。在RPC的运营中,序列化问题恐怕是我碰到的和解答过的最多的问题了,经常有业务会向我反馈这个问题,比如某个类型为集合类的入参服务调用者不能解析了,服务提供方将入参类加一个属性之后服务调用方不能正常调用,升级了RPC版本后发起调用时报序列化异常了…

在序列化的选择上,与序列化协议的效率、性能、序列化协议后的体积相比,其通用性和兼容性的优先级会更高,因为他是会直接关系到服务调用的稳定性和可用率的,对于服务的性能来说,服务的可靠性显然更加重要。我们更加看重这种序列化协议在版本升级后的兼容性是否很好,是否支持更多的对象类型,是否是跨平台、跨语言的,是否有很多人已经用过并且踩过了很多的坑,其次我们才会去考虑性能、效率和空间开销。

还有一点我要特别强调。除了序列化协议的通用性和兼容性,序列化协议的安全性也是非常重要的一个参考因素,甚至应该放在第一位去考虑。以JDK原生序列化为例,它就存在漏洞。如果序列化存在安全漏洞,那么线上的服务就很可能被入侵。

综合上面几个参考因素,现在我们再来总结一下这几个序列化协议。

我们首选的还是Hessian与Protobuf,因为他们在性能、时间开销、空间开销、通用性、兼容性和安全性上,都满足了我们的要求。其中Hessian在使用上更加方便,在对象的兼容性上更好;Protobuf则更加高效,通用性上更有优势。

四、RPC框架在使用时要注意哪些问题?

了解了在RPC框架中如何选择序列化,那么我们在使用过程中需要注意哪些序列化上的问题呢?

最多的问题就是序列化问题了,除了早期RPC框架本身出现的问题以外,大多数问题都是使用方使用不正确导致的,接下来我们就盘点下这些高频出现的人为问题。

  • 对象构造得过于复杂:属性很多,并且存在多层的嵌套,比如A对象关联B对象,B对象又聚合C对象,C对象又关联聚合很多其他对象,对象依赖关系过于复杂。序列化框架在序列化与反序列化对象时,对象越复杂就越浪费性能,消耗CPU,这会严重影响RPC框架整体的性能;另外,对象越复杂,在序列化与反序列化的过程中,出现问题的概率就越高。

  • 对象过于庞大:我经常遇到业务过来咨询,为啥他们的RPC请求经常超时,排查后发现他们的入参对象非常得大,比如为一个大List或者大Map,序列化之后字节长度达到了上兆字节。这种情况同样会严重地浪费了性能、CPU,并且序列化一个如此大的对象是很耗费时间的,这肯定会直接影响到请求的耗时。

  • 使用序列化框架不支持的类作为入参类:比如Hessian框架,他天然是不支持LinkedHashMap、LinkedHashSet等,而且大多数情况下最好不要使用第三方集合类,如Guava中的集合类,很多开源的序列化框架都是优先支持编程语言原生的对象。因此如果入参是集合类,应尽量选用原生的、最为常用的集合类,如HashMap、ArrayList。

  • 对象有复杂的继承关系:大多数序列化框架在序列化对象时都会将对象的属性一一进行序列化,当有继承关系时,会不停地寻找父类,遍历属性。就像问题1一样,对象关系越复杂,就越浪费性能,同时又很容易出现序列化上的问题。

在RPC框架的使用过程中,我们要尽量构建简单的对象作为入参和返回值对象,避免上述问题。

五、总结

如何选择序列化协议

  • RPC框架中如何去选择序列化协议,我们有这样几个很重要的参考因素,优先级从高到低依次是安全性、通用性和兼容性,之后我们会再考虑序列化框架的性能、效率和空间开销。

  • 这归根结底还是因为服务调用的稳定性与可靠性,要比服务的性能与响应耗时更加重要。另外对于RPC调用来说,整体调用上,最为耗时、最消耗性能的操作大多都是服务提供者执行业务逻辑的操作,这时序列化的开销对于服务整体的开销来说影响相对较小。

使用RPC框架的最佳实践

  • 对象要尽量简单,没有太多的依赖关系,属性不要太多,尽量高内聚;

  • 入参对象与返回值对象体积不要太大,更不要传太大的集合;

  • 尽量使用简单的、常用的、开发语言原生的对象,尤其是集合类;

  • 对象不要有复杂的继承关系,最好不要有父子类的情况。

实际上,虽然RPC框架可以让我们发起远程调用就像调用本地一样,但在RPC框架的传输过程中,入参与返回值的根本作用就是用来传递信息的,为了提高RPC调用整体的性能和稳定性,我们的入参与返回值对象要构造得尽量简单,这很重要。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/321788.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Jenkins-Maven Git

整合Maven 安装GIT #更新yum sudo yum update #安装git yum install git 安装Maven插件,在插件管理中心: 配置仓库 配置密码认证 我们可以在这个目录下看到Jenkins 帮我们拉取了代码 /env/liyong/data/docker/jenkins_mount/workspace/maven-job 配置maven打包…

江科大STM32 下

目录 ADC数模转换器DMA直接存储器存取USART串口9-2 串口发送接受9-3 串口收发HEX数据包 I2CSPI协议10.1 SPI简介W25Q64简介10.3 SPI软件读写W25Q6410.4 SPI硬件读写W25Q64 BKP、RTC11.0 Unix时间戳11.1 读写备份寄存器BKP11.2 RTC实时时钟 十二、PWR12.1 PWR简介12.2 修改主频1…

Scratch优秀作品飞翔小鸟

程序说明:在无尽的划痕堆中飞驰而过随着你越来越多地飞进迷宫般的街区,平台变得越来越难。 演示视频 scratch飞翔小鸟 其实这就是一个类似像素小鸟的程序,只不过水管角色就地取材,使用scratch里面的积木图片拼成了水管&#xff0…

爬虫案例—抓取豆瓣电影的电影名称、评分、简介、评价人数

爬虫案例—抓取豆瓣电影的电影名称、评分、简介、评价人数 豆瓣电影网址:https://movie.douban.com/top250 主页截图和要抓取的内容如下图: 分析: 第一页的网址:https://movie.douban.com/top250?start0&filter 第二页的…

文献阅读:Large Language Models as Optimizers

文献阅读:Large Language Models as Optimizers 1. 文章简介2. 方法介绍 1. OPRO框架说明2. Demo验证 1. 线性回归问题2. 旅行推销员问题(TSP问题) 3. Prompt Optimizer 3. 实验考察 & 结论 1. 实验设置2. 基础实验结果 1. GSM8K2. BBH3.…

linux建立基本网站

网站需求: 1.基于域名[www.openlab.com]可以访问网站内容为 welcome to openlab!!! 2.给该公司创建三个子界面分别显示学生信息,教学资料和缴费网站,基于[www.openlab.com/student] 网站访问学生信息 [www.openlab.com/data]网站访问教学资…

微机原理常考填空以及注意事项

以下: 1,两条高位地址线未参加地址译码,则对应的地址范围它的容量是多少倍? 答:公式CPU的地址线(假设16位)(它的低位地址线一般进入片内A0~A10,高位A11就是A、A12就是B、…

微信小程序(一)简单的结构及样式演示

注释很详细&#xff0c;直接上代码 涉及内容&#xff1a; view和text标签的使用类的使用flex布局水平方向上均匀分布子元素垂直居中对齐子元素字体大小文字颜色底部边框的宽和颜色 源码&#xff1a; index.wxml <view class"navs"><text class"active…

任务7:安装MySQL数据库

任务描述 知识点&#xff1a; MySQL数据库安装与使用 重 点&#xff1a; 基于CentOS系统&#xff0c;安装MySQL数据库 内 容&#xff1a; 安装MySQL数据库修改root用户密码 任务指导 MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB 公司开发&#xff0c…

【汽车销售数据】2015~2023年各厂商各车型的探索 数据分析可视化

数据处理的思路&#xff1a; 1 各表使用情况&#xff1a; 汽车分厂商每月销售表&#xff0c;该表主要分析展示top10销量的厂商销量、占比变化情况&#xff08;柱形图、饼图&#xff09;&#xff1b;中国汽车分车型每月销售量表&#xff0c;该表主要分析展示top20销量的车型销…

UML-顺序图

提示&#xff1a;用例图从参与者的角度出发&#xff0c;描述了系统的需求&#xff08;用例图&#xff09;&#xff1b;静态图定义系统中的类和对象间的静态关系&#xff08;类图、对象图和包图&#xff09;&#xff1b;状态机模型描述系统元素的行为和状态变化流程&#xff08;…

计算机体系结构基础复习

1. 计算机系统可划分为哪几个层次,各层次之间的界面是什么? 你认为这样划分层次的意义何在? 答&#xff1a; 计算机系统可划分为四个层次&#xff0c;分别是&#xff1a;应用程序、 操作系统、 硬件系统、 晶体管四个大的层次。 注意把这四个层次联系起来的三个界面。各层次…

css 怎么绘制一个带圆角的渐变色的边框

1&#xff0c;可以写两个样式最外面的div设置一个渐变的背景色。里面的元素使用纯色。但是宽高要比外面元素的小。可以利用里面的元素设置padding这样挡住部分渐变色。漏出来的渐变色就像边框一样。 <div class"cover-wrapper"> <div class"item-cover…

春节回家前,请一定给你的电脑装上KKView远程控制软件

马上春节了&#xff0c;电脑不能带回家&#xff0c;有时候要处理点意外的事情&#xff0c;怎么办&#xff1f;只要走之前&#xff0c;给你电脑装上KKView远程控制软件&#xff0c;就可以随时随地用手机或电脑控制你的工作电脑&#xff0c;远程办公、传文件、看摄像头都没问题。…

人脸识别为何老是不过?是什么原因导致的?

人脸识别可能无法通过的原因有很多&#xff0c;以下是可能的一些原因&#xff1a; 1. 非常规面部表情&#xff1a;如果你做出了与常规面部表情不同的表情&#xff0c;如张大嘴巴或瞪大眼睛等&#xff0c;可能会干扰人脸识别系统的准确性。 2. 光线条件&#xff1a;人脸识别系统…

30 3D导航栏

效果演示 实现了一个导航栏&#xff0c;其中包含了五个图标&#xff0c;每个图标都有一个悬浮的文字标签&#xff0c;当鼠标悬停在图标上时&#xff0c;文字标签会旋转并向上移动&#xff0c;同时底部会出现一个阴影效果。整个导航栏的背景颜色为浅灰色。 Code <ul><…

js(JavaScript)数据结构之堆(Heap)

什么是数据结构&#xff1f; 下面是维基百科的解释&#xff1a; 数据结构是计算机存储、组织数据的方式。数据结构意味着接口或封装&#xff1a;一个数据结构可被视为两个函数之间的接口&#xff0c;或者是由数据类型联合组成的存储内容的访问方法封装。 我们每天的编码中都会…

docker安装部署Elasticsearch(ES)以及相关配置

Elasticsearch简介 mysql用作持久化存储&#xff0c;ES用作检索 基本概念&#xff1a;index库>type表>document文档 index索引&#xff08;相当于MySQL的数据库&#xff09; 动词&#xff1a;相当于mysql的insert 名词&#xff1a;相当于mysql的db Type类型&#xff…

Spring Cloud整体架构解析

Spring Cloud整体架构 本文已收录至我的个人网站&#xff1a;程序员波特&#xff0c;主要记录Java相关技术系列教程&#xff0c;共享电子书、Java学习路线、视频教程、简历模板和面试题等学习资源&#xff0c;让想要学习的你&#xff0c;不再迷茫。 Spring Cloud的中文名我们就…

Flask架构--路由和蓝图

学习视频&#xff1a;第二章&#xff1a;路由和蓝图 1 Flask查询路由的方式_哔哩哔哩_bilibili 参考&#xff1a;Flask框架之路由与蓝图的使用_flask 路由和蓝图-CSDN博客 1.路由的概念&#xff1a; 用于将http请求与特定的python函数相匹配。定义路由后&#xff0c;flask程…