对象在网络中如何传输之序列化

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

为什么要序列化

因为在网络中传输的数据只能是二进制。

序列化就是将对象转换成二进制,反序列化就是讲二进制转化为对象的过程。

序列化和反序列化过程如下
在这里插入图片描述

RPC 框架为什么需要序列化?

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

RPC 的通信流程在这里插入图片描述

常见的序列化

JDK原生序列化

序列化的实现是由ObjectOutputStream完成,反序列化由ObjectInputStream完成。

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);

    }
}

JDK的序列化过程如下
在这里插入图片描述
序列化过程就是在读取对象数据的时候,不断的加入一些特殊分隔符,这些特殊分隔符在反序列化中使用。

  • 头部数据用来声明序列化协议、版本,用于高版本向后兼容
  • 对象数据主要包括类名、签名、属性名、属性类型及属性值、开头结尾数据
  • 存在对象引用、继承的情况下,递归遍历写对象逻辑

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

JSON

JSON是典型的key-value形式,没有数据类型,是一种文本型序列化框架
基于HTTP的RPC通信框架会采用JSON格式,存在以下问题

  • JSON序列化的额外空间开销比较大,对于大数据量服务这意味着巨大的内存和磁盘开销,所以选择JSON的时候,数据量要小。
  • JSON没有类型,像Java这种强类型语言,需要反射统一解决,性能不太好
Hessian

Hession是动态类型、二进制、紧凑的、可跨语言移植的一种序列化框架。

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);

存在问题:主要集中在对Java一些常见类型不支持

  • linked系列:LinkedHashMap、LinkedHashSet不支持,可以通过CollectionDesric
  • Locale类:通过扩展ContextSerializerFactory类修复
  • Byte/Short反序列化为Integer
Protobuf

Google内部的混合语言数据标准,是一种轻便、高效的结构化数据存储格式,可以用于结构化数据序列化,支持 Java、Python、C++、Go 等语言。

Protobuf 使用的时候需要定义 IDLProtobuf使用的时候需要定义IDL文件,使用不用语言的IDL编译器,生成序列化工具类。

  • 序列化体积比JSON、Hessian要小
  • IDL能清晰的描述语义,所以足以帮助并保证应用程序之间的类型不会丢失,无需类似 XML 解析器;
  • 序列化、反序列化速度很快,不需要反射获取类型
  • 消息格式升级和兼容性不错,可以做到向后兼容。
// IDl 文件格式
synax = "proto3";
option java_package = "com.test";
option java_outer_classname = "StudentProtobuf";
message StudentMsg {
	int32no=1;
 	//姓名
 	string name = 2;
}
StudentProtobuf.StudentMsg.Builder builder = StudentProtobuf.StudentMsg.newBuilder();
builder.setNo(103);
builder.setName("protobuf");
//把student对象转化为byte数组 StudentProtobuf.StudentMsg msg = builder.build(); byte[] data = msg.toByteArray();
//把刚才序列化出来的byte数组转化为student对象
StudentProtobuf.StudentMsg deStudent = StudentProtobuf.StudentMsg.parseFrom(dat
System.out.println(deStudent);

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

Protostuff

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

  • 不支持null
  • 不支持淡村的Map、List集合对象,需要包在对象里。

如何选择序列化方式

  • RPC序列化框架的性能和效率
  • 空间开销:序列化之后的二进制数据的体积大小
  • 序列化协议的通用性和兼容性:多个语言,多个版本
  • 序列化协议的安全性:JDK 原生序列化存在安全漏洞

在这里插入图片描述

首选的还是 Hessian 与 Protobuf,因为他们在性能、时间开销、空间开销、通用性、 兼容性和安全性上,都满足了我们的要求。

  • Hessian 在使用上更加方便,在对象的兼 容性上更好;
  • Protobuf 则更加高效,通用性上更有优势。

RPC使用过程中注意哪些问题

  • 对象要尽量简单,没有太多的依赖关系
  • 入参和返回值体积不要太大
  • 尽量使用简单的、常用的原生对象。
  • 对象不要有复杂的继承关系。

总结

序列化框架的本质就是一个序列化的协议,将对象的类型、属性、属性值按照归你管的格式写到二进制流中来完成序列化,再按照固定的格式读出对象的类型、属性类型、属性值,通过这类信息重新创建出一个新的对象,来完成反序列化。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值