网络传输的数据必须是二进制数据,但调用方请求的出入参数都是对象。对象是不能直接在网络中传输的,所以我们需要提前把它转换成可传输的二进制,并且要求转换算法是可逆的,这个过程我们叫做“序列化”。这时,服务提供方就可以正确的从二进制数据中分隔出不同的请求,同时根据请求类型和序列化类型,把二进制的消息体逆向还原成请求对象,这个过程叫做“反序列化”
总结来说,序列化就是将对象转换成二进制数据的过程,而反序列就是反过来将二进制转换为对象的过程。
那么 RPC 框架为什么需要序列化呢? RPC 的通信流程如下:
举个例子:发快递,我们要发一个需要自行组装的物件。发件人发之前,会把物件拆开装箱,这就好比序列化;这时候快递员来了,不能磕碰呀,那就要打包,这就好比将序列化后的数据进行编码,封装成一个固定格式的协议;过了两天,收件人收到包裹了,就会拆箱将物件拼接好,这就好比是协议解码和反序列化。
也就是说,因为网络传输的数据必须是二进制数据,所以在RPC调用中,对入参对象和返回值对象进行序列化和反序列化是一个必须的过程
在不同的场景下合理地选择序列化方式,对提升 RPC 框架整体的稳定性和性能是至关重要的。
public class Student implements Serializable {
private int number;
private String name;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"number=" + number +
", 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.text");
Student student=new Student();
student.setName("wangkai");
student.setNumber(1);
ObjectOutputStream oos=new ObjectOutputStream(fos);
oos.writeObject(student);
oos.flush();;
oos.flush();
FileInputStream fis=new FileInputStream(basePath+"student.text");
ObjectInputStream ois=new ObjectInputStream(fis);
Student student1=(Student) ois.readObject();
ois.close();
System.out.println(student1);
}
}
序列化具体的实现是由 ObjectOutputStream 完成的,而反序列化的具体实现是由 ObjectInputStream 完成的。
那么 JDK 的序列化过程是怎样完成的呢?我们看下下面这张图:
序列化过程就是在读取对象数据的时候,不断加入一些特殊分隔符,这些特殊分隔符用于在反序列化过程中阶段用
实际上任何一种序列化框架,核心思想就是设计一种序列化协议,将对象的类型、属性类型、属性值一一按照固定的格式写到二进制字节流中完成序列化,再按照固定的格式意义读出对象的类型、属性类型、属性值,通过这些信息重新创建出一个新的对象,来完成反序列化
想到了Redis使用的RESP,在做序列化时也是会增加很多冗余的字符,但它胜在实现简单、可读性强易于理解
JSON是典型的key-value方式,没有数据类型,是一种文本型序列化框架,在应用上非常广泛,无论是web还是磁盘存储文本类型的数据,还是基于HTTP协议的RPC框架,都会选择JSON格式。
但是注意两个问题:
所以,如果RPC框架选用json序列化,服务提供者和服务调用者之间传输的数据量相对要小,否则将严重影响性能
hessian是动态类型、二进制、紧凑的,并且可跨语言移植的一种序列化框架。hessian协议要比jdk、json更加紧凑、性能更高效、生成的字节数更小,有非常好的兼容性和稳定性,所以hessian更合适作为RPC框架通信的序列化协议
protobuf是google公司内部使用的混合语言数据标准,是一种轻便、高效的结构化数据存储格式,可以用于结构化数据序列号,支持java、python、c++、go等语言。
protobuf使用的时候需要定义 IDL(Interface description language),然后使用不同语言的 IDL编译器,生成序列化工具类,它的优点是:
Protobuf 非常高效,但是对于具有反射和动态能力的语言来说,这样用起来很费劲,这一点就不如 Hessian,比如用 Java 的话,这个预编译过程不是必须的,可以考虑使用Protostuff。
Protostuff 不需要依赖 IDL 文件,可以直接对Java 领域对象进行反 / 序列化操作,在效上跟 Protobuf 差不多,生成的二进制格式和Protobuf 是完全相同的,可以说是一个 Java版本的 Protobuf 序列化框架
总结下序列化协议可以分为两类
1.文本类序列化方式,如 xml,json。优点就是可读性好,构造方便,调试也简单。不过缺点也明显,传输体积大,性能差。
2.二进制类学序列化方式,如 Hessian,Protobuf,优点性能好
必须考虑下面几点
(1)序列化协议的安全性、通用性和兼容性:这直接关系到服务调用的稳定性和可用率的
(2)性能和效率,序列化与反序列化过程是 RPC 调用的一个必须过程,那么序列化与反序列化的性能和效率势必将直接关系到 RPC 框架整体的性能和效率。
(3)空间开销,也就是序列化之后的二进制数据的体积大小。序列化后的字节数据体积越小,网络传输的数据量越小,传输数据的速度也就越快,由于RPC是远程调用,那么网络传输的速度将直接关系到请求响应的耗时。
综上:首选的还是 Hessian 与 Protobuf,因为他们在性能、时间开销、空间开销、通用性、兼容性和安全性上,都满足了我们的要求。其中 Hessian 在使用上更加方便,在对象的兼容性上更好;Protobuf 则更加高效,通用性上更有优势
也就是说,在使用 RPC 框架的过程中,我们构造入参、返回值对象,主要记住以下几点:
实际上,虽然RPC框架可以让我们发起远程调用就像本地调用一样,但是在RPC框架的传输过程中,入参和返回值的根本作用是用来传递信息的,为了提高RPC调用整体的性能和稳定性,我们的入参和返回值对象要构造得尽量简单,这很重要