一年前,笔者刚刚接触 RPC 框架,从单体式应用向分布式应用的变革无疑是让人兴奋的,同时也对 RPC 背后到底做了哪些工作产生了兴趣,但其底层的设计对新手而言并不是很友好,其涉及的一些常用技术点都有一定的门槛。如传输层常常使用的 netty,之前完全没听过,想要学习它,需要掌握前置知识点 nio;协议层,包括了很多自定义的协议,而每个 RPC 框架的实现都有差异;代理层的动态代理技术,如 jdk 动态代理,虽然实战经验不多,但至少还算会用,而 cglib 则又有一个盲区;序列化层倒还算是众多层次中相对简单的一环,但 RPC 为了追求可扩展性,性能等诸多因素,通常会支持多种序列化方式以供使用者插拔使用,一些常用的序列化方案 hessian,kryo,Protobuf 又得熟知…
这个系列打算就 RPC 框架涉及到的一些知识点进行探讨,本篇先从序列化层的一种选择 –kryo 开始进行介绍。
序列化概述
大白话介绍下 RPC 中序列化的概念,可以简单理解为对象 –> 字节的过程,同理,反序列化则是相反的过程。为什么需要序列化?因为网络传输只认字节。所以互信的过程依赖于序列化。有人会问,FastJson 转换成字符串算不算序列化?对象持久化到数据库算不算序列化?没必要较真,广义上理解即可。
JDK 序列化
可能你没用过 kryo,没用过 hessian,但你一定用过 jdk 序列化。我最早接触 jdk 序列化,是在大二的 JAVA 大作业中,《XX 管理系统》需要把对象保存到文件中(那时还没学数据库),jdk 原生支持的序列化方式用起来也很方便。
class Student implements Serializable{
private String name;
}
class Main{
public static void main(String[] args) throws Exception{
// create a Student
Student st = new Student("kirito");
// serialize the st to student.db file
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.db"));
oos.writeObject(st);
oos.close();
// deserialize the object from student.db
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student.db"));
Student kirito = (Student) ois.readObject();
ois.close();
// assert
assert "kirito".equals(kirito.getName());
}
}
Student 实体类需要实现 Serializable 接口,以告知其可被序列化。
序列化协议的选择通常有下列一些常用的指标:
- 通用性。是否只能用于 java 间序列化 / 反序列化,是否跨语言,跨平台。
- 性能。分为空间开销和时间开销。序列化后的数据一般用于存储或网络传输,其大小是很重要的一个参数;解析的时