序列化

序列化与反序列化

序列化与反序列化

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

常见序列化

http://image.clickear.top/20211008170440.png

  • Hession2(dubbo默认)
  • Protostubuf、ProtoBuf、Thrift、Avro(跨语言)
  • Kyro、FST(专门支持java)

建议使用Kryo

可参考: serialization

常见序列化算法

Apache Avro 是一种与编程语言无关的序列化格式。Avro 依赖于用户自定义的 Schema,在进行序列化数据的时候,无须多余的开销,就可以快速完成序列化,并且生成的序列化数据也较小。当进行反序列化的时候,需要获取到写入数据时用到的 Schema。在 Kafka、Hadoop 以及 Dubbo 中都可以使用 Avro 作为序列化方案。

FastJson 是阿里开源的 JSON 解析库,可以解析 JSON 格式的字符串。它支持将 Java 对象序列化为 JSON 字符串,反过来从 JSON 字符串也可以反序列化为 Java 对象。FastJson 是 Java 程序员常用到的类库之一,正如其名,“快”是其主要卖点。从官方的测试结果来看,FastJson 确实是最快的,比 Jackson 快 20% 左右,但是近几年 FastJson 的安全漏洞比较多,所以你在选择版本的时候,还是需要谨慎一些。

Fst(全称是 fast-serialization)是一款高性能 Java 对象序列化工具包,100% 兼容 JDK 原生环境,序列化速度大概是JDK 原生序列化的 4~10 倍,序列化后的数据大小是 JDK 原生序列化大小的 1/3 左右。目前,Fst 已经更新到 3.x 版本,支持 JDK 14。

Kryo 是一个高效的 Java 序列化/反序列化库,目前 Twitter、Yahoo、Apache 等都在使用该序列化技术,特别是 Spark、Hive 等大数据领域用得较多。Kryo 提供了一套快速、高效和易用的序列化 API。无论是数据库存储,还是网络传输,都可以使用 Kryo 完成 Java 对象的序列化。Kryo 还可以执行自动深拷贝和浅拷贝,支持环形引用。Kryo 的特点是 API 代码简单,序列化速度快,并且序列化之后得到的数据比较小。另外,Kryo 还提供了 NIO 的网络通信库——KryoNet,你若感兴趣的话可以自行查询和了解一下。

Hessian2 序列化是一种支持动态类型、跨语言的序列化协议,Java 对象序列化的二进制流可以被其他语言使用。Hessian2 序列化之后的数据可以进行自描述,不会像 Avro 那样依赖外部的 Schema 描述文件或者接口定义。Hessian2 可以用一个字节表示常用的基础类型,这极大缩短了序列化之后的二进制流。需要注意的是,在 Dubbo 中使用的 Hessian2 序列化并不是原生的 Hessian2 序列化,而是阿里修改过的 Hessian Lite,它是 Dubbo 默认使用的序列化方式。其序列化之后的二进制流大小大约是 Java 序列化的 50%,序列化耗时大约是 Java 序列化的 30%,反序列化耗时大约是 Java 序列化的 20%。

Protobuf(Google Protocol Buffers)是 Google 公司开发的一套灵活、高效、自动化的、用于对结构化数据进行序列化的协议。但相比于常用的 JSON 格式,Protobuf 有更高的转化效率,时间效率和空间效率都是 JSON 的 5 倍左右。Protobuf 可用于通信协议、数据存储等领域,它本身是语言无关、平台无关、可扩展的序列化结构数据格式。目前 Protobuf提供了 C++、Java、Python、Go 等多种语言的 API,gRPC 底层就是使用 Protobuf 实现的序列化

protostuff

dubbo-serialization

http://image.clickear.top/20211104113616.png

dubbo-serialization-api 模块中定义了 Dubbo 序列化层的核心接口,其中最核心的是 Serialization 这个接口,它是一个扩展接口,被 @SPI 接口修饰,默认扩展实现是 Hessian2Serialization

@SPI("hessian2") // 被@SPI注解修饰,默认是使用hessian2序列化算法
public interface Serialization {
    // 每一种序列化算法都对应一个ContentType,该方法用于获取ContentType
    String getContentType();
    
    // 获取ContentType的ID值,是一个byte类型的值,唯一确定一个算法
    byte getContentTypeId();
    // 创建一个ObjectOutput对象,ObjectOutput负责实现序列化的功能,即将Java
    // 对象转化为字节序列
    @Adaptive
    ObjectOutput serialize(URL url, OutputStream output) throws IOException;
    // 创建一个ObjectInput对象,ObjectInput负责实现反序列化的功能,即将
    // 字节序列转换成Java对象
    @Adaptive
    ObjectInput deserialize(URL url, InputStream input) throws IOException;
}

Hessian2ObjectOutput (对象转换成字节序列)

DataOutput 接口中定义了序列化 Java 中各种数据类型的相应方法,其中有序列化 boolean、short、int、long 等基础类型的方法,也有序列化 String、byte[] 的方法。

http://image.clickear.top/20211104115213.png

http://image.clickear.top/20211104115330.png

Hessian2ObjectOutput implement ObjectOutput extends DataOutput

public class Hessian2ObjectOutput implements ObjectOutput {
    private static ThreadLocal<Hessian2Output> OUTPUT_TL = ThreadLocal.withInitial(() -> {
        // 初始化Hessian2Output对象
        Hessian2Output h2o = new Hessian2Output(null);        h2o.setSerializerFactory(Hessian2SerializerFactory.SERIALIZER_FACTORY);
        h2o.setCloseStreamOnClose(true);
        return h2o;
    });
    private final Hessian2Output mH2o;
    public Hessian2ObjectOutput(OutputStream os) {
        mH2o = OUTPUT_TL.get(); // 触发OUTPUT_TL的初始化
        mH2o.init(os);
    }
    public void writeObject(Object obj) throws IOException {
        mH2o.writeObject(obj);
    }
    ... // 省略序列化其他类型数据的方法
}

Hessian2ObjectInput(字节码转换成对象)

http://image.clickear.top/20211104115858.png

http://image.clickear.top/20211104115933.png

Hessian2ObjectInput implement ObjectInput extends DataInput

public class Hessian2ObjectInput implements ObjectInput {
    private final Hessian2Input mH2i;
    public Hessian2ObjectInput(InputStream is) {
        mH2i = new Hessian2Input(is);
        mH2i.setSerializerFactory(Hessian2SerializerFactory.SERIALIZER_FACTORY);
    }
    ... // 省略序列化其他类型数据的方法
    @Override
    public Object readObject() throws IOException {
        return mH2i.readObject();
    }
}

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

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

  1. 对象要尽量简单,没有太多的依赖关系,属性不要太多,尽量高内聚;
  2. 入参对象与返回值对象体积不要太大,更不要传太大的集合;
  3. 尽量使用简单的、常用的、开发语言原生的对象,尤其是集合类;
  4. 对象不要有复杂的继承关系,最好不要有父子类的情况

实现

KryoSerialization.java

dubbo默认hessian2,未来,Kryo或者FST成熟后,会使用其中之一

性能对比

参考apacha-dubbo序列化

参考

资料

Kryo 和 FST 序列化 | Apache Dubbo
layering-cache序列化对比