十 网络通信优化之序列化:避免使用Java序列化

215 阅读5分钟

大家好,我是小水珠。

当前大部分后端服务都是基于微服务架构实现的。服务按照业务划分被拆分,实现了服务的解耦,但同时也带来了新的问题,不同业务之间通信需要通过接口实现调用。两个服务之间要共享一个数据对象,就需要从对象转换成二进制流,通过网络传输,出送到对方服务,再转换回对象,供服务方法调用。这个编码和解码过程我们称之为序列化和反序列化。

在大量并发请求的情况下,如果序列化的速度慢,会导致请求的响应时间增加;而序列化后的传输体积大,会导致网络吞吐量下降。所以一个优秀的序列化框架可以提高系统的整体性能。

我们知道,Java提供了RMI框架可以实现服务间的接口暴露和调用,RMI中对数据对象的序列化采用的是Java的序列化。而目前主流的微服务框架却计划没有用到Java序列化,SpringCloud用的是Json序列化,Dubbo虽然兼容了Java序列化,但默认使用的是Hessian序列化。这是为什么呢?

今天我们就来深入了解下Java序列化,再对比近两年来比较火的Protobuf序列化,看看Protobuf是如何实现最优序列化的。

一 Java序列化

java提供了一种序列化机制,这种机制能够将一个对象序列化为二进制形式(字节数组),用于写入磁盘或输出到网络,同时也能从网络或磁盘中读取字节数组,反序列成对象,在程序中使用。

具体实现序列化的是writeObject和readObject,通常这两个方法是默认的,当然我们也可以在实现Serializable接口的类中对其进行重写,定制一套属于自己的序列化和反序列化机制。

另外,Java序列化的类中还定义了两个重写方法:writeReplace和readResolve,前者是用来在序列化之前替换序列化对象的,后者是用来在反序列化之后对返回对象进行处理的。

二 Java序列化的缺陷

1.无法跨语言

2.易被攻击

微信图片_20220804233257.jpg

2015年FoxGlove Security安全团队的breenmachine发布过一遍长博客,主要内容是:通过Apache Commons Collections,Java反序列化漏洞可以实现攻击。一度横扫了WebLogic,WebSphere,JBoss,Jenkins,OpenNMS的最新版,各大Java Web Server纷纷躺枪。

其实Apache Commons Collections允许链式的任意的类函数反射调用,攻击者通过“实现序列化协议”的端口,把攻击代码传到服务器上,再由Apache Commons Collections里的TransformedMap来执行。

那后来是如何解决这个漏洞的呢?

微信图片_202208042332571.jpg

3.序列化后的流太大

Java序列化中使用了ObjectOutputStream来实现对象转二进制编码,那么这种序列化机制实现的二进制编码完成的二进制数组大小,相比于NIO中的ByteBuffer实现的二进制编码完成的数组大小,有没有区别呢?

微信图片_202208042332572.jpg

运行结果:

ObjectOutputStream 字节编码长度:99

ByteBuffer 字节编码长度:16

4.序列化性能太差

序列化的速度也是体现序列化性能的重要指标,如果序列化的速度慢,就会影响网络通信的效率,从而增加系统的响应时间。我们再来通过上面的例子,来对比下Java序列化与NIO中的ByteBuffer编码的性能:

微信图片_202208042332573.jpg

运行结果:

ObjectOutputStream 序列化时间:29

ByteBuffer 序列化时间:6

三 使用Protobuf序列化替换Java序列化

Protobuf是由谷歌推出且支持多语言的序列化框架,目前主流网站上的序列化框架性能对比测试报告中,Protobuf无论是编码耗时,还是二进制流压缩大小,都名列前茅。

Protobuf是以一个.proto后缀的文件为基础,这个文件描述了字段以及字段类型,通过工具可以生成不同语言的数据结构文件。在序列化该对象的时候,Protobuf通过.proto文件描述来生成Protobuf Buffers格式的编码。

四 总结

无论是网络传输还是磁盘持久化数据,我们都需要将数据编码成字节码,而我们平时在程序中使用的数据都是基于内存的数据类型或对象,我们要通过编码将这些数据转化成二进制字节流;如果需要接收或者再使用时,又需要通过解码将二进制字节流转换成内存数据。我们通常将这两个过程称为序列化与反序列化。

Java默认的序列化是通过Serializable接口实现的,只要实现了该接口,同时生成一个默认的版本号,我们无需手动设置,该类就会自动实现序列化与反序列化。

Java默认的序列化虽然实现方便,但却存在安全漏洞,不跨语言以及性能差等缺陷,所以我强烈建议你避免使用Java序列化。

纵观主流序列化框架,FastJson,protobuf,kryo是比较有特点的,而且性能以及安全方面都得到了业界的认可,我们可以结合自身业务来选择一种合适的序列化框架,来优化系统的序列化性能。