首页 > 其他分享 >Dubbo——Serialize 层分析

Dubbo——Serialize 层分析

时间:2022-10-19 11:05:03浏览次数:64  
标签:分析 Dubbo Java 对象 Serialize 接口 序列化 public

前言

我们知道一个 RPC 框架需要通过网络通信实现跨 JVM 的调用。既然需要网络通信,那就必然会使用到序列化与反序列化的相关技术,Dubbo 也不例外。下面我们从 Java 序列化的基础内容开始,介绍一下常见的序列化算法,最后再分析一下 Dubbo 是如何支持这些序列化算法的。

一、Java 序列化基础

Java 中的序列化操作一般有如下四个步骤。

  • 1.1、被序列化的对象需要实现 Serializable 接口
public class Student implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String name;
    
    private int age;
    
    private transient StudentUtil studentUtil;
}
  • transient 关键字的作用 在对象序列化过程中忽略被其修饰的成员属性变量。可用来修饰一些非数据型的字段以及一些可以通过其他字段计算得到的值。合理使用 transient,可降低序列化后的数据量,提高网络传输效率。

  • 1.2、生成 serialVersionUID 生成一个序列号 serialVersionUID。该序列号非必需,但推荐生成。serialVersionUID 字面含义是序列化的版本号,只有序列化和反序列化的 serialVersionUID 都相同的情况下,才能够成功地反序列化。若类中没有定义 serialVersionUID,那么 JDK 也会随机生成一个 serialVersionUID。如果在某些场景中,你希望不同版本的类序列化和反序列化相互兼容,那就需要定义相同的 serialVersionUID。

  • 1.3 重写 writeObject()/readObject() 根据需求决定是否要重写 writeObject()/readObject() 方法,实现自定义序列化。

  • 1.4 调用writeObject()/readObject() 调用 java.io.ObjectOutputStream 的 writeObject()/readObject() 进行序列化与反序列化。

Java 本身的序列化操作简单,但第三方序列化框架的速度更快、序列化的效率更高,而且支持跨语言操作。

二、常见序列化算法

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 实现的序列化。

三、dubbo-serialization

Dubbo 为了支持多种序列化算法,单独抽象了一层 Serialize 层,在整个 Dubbo 架构中处于最底层,对应的模块是 dubbo-serialization 模块。

定义了 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;
}

Dubbo 提供了多个 Serialization 接口实现,用于接入各种各样的序列化算法,如下图所示:

这里我们以默认的 hessian2 序列化方式为例,介绍 Serialization 接口的实现以及其他相关实现。 Hessian2Serialization 实现如下所示:

public class Hessian2Serialization implements Serialization {
    public byte getContentTypeId() {
        return HESSIAN2_SERIALIZATION_ID; // hessian2的ContentType ID
    }
    public String getContentType() { // hessian2的ContentType
        return "x-application/hessian2";
    }
    public ObjectOutput serialize(URL url, OutputStream out) throws IOException { // 创建ObjectOutput对象
        return new Hessian2ObjectOutput(out);
    }
    public ObjectInput deserialize(URL url, InputStream is) throws IOException { // 创建ObjectInput对象
        return new Hessian2ObjectInput(is);
    }
}

Hessian2Serialization 中的 serialize() 方法创建的 ObjectOutput 接口实现为 Hessian2ObjectOutput,继承关系如下图所示:

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

ObjectOutput 接口继承了 DataOutput 接口,并在其基础之上,添加了序列化对象的功能,具体定义如下图所示,其中的 writeThrowable()、writeEvent() 和 writeAttachments() 方法都是调用 writeObject() 方法实现的。

Hessian2ObjectOutput 中会封装一个 Hessian2Output 对象,需要注意,这个对象是 ThreadLocal 的,与线程绑定。在 DataOutput 接口以及 ObjectOutput 接口中,序列化各类型数据的方法都会委托给 Hessian2Output 对象的相应方法完成,实现如下:

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);
    }
    ... // 省略序列化其他类型数据的方法
}

Hessian2Serialization 中的 deserialize() 方法创建的 ObjectInput 接口实现为 Hessian2ObjectInput,继承关系如下所示:

Hessian2ObjectInput 具体的实现与 Hessian2ObjectOutput 类似:在 DataInput 接口中实现了反序列化各种类型的方法,在 ObjectInput 接口中提供了反序列化 Java 对象的功能,在 Hessian2ObjectInput 中会将所有反序列化的实现委托为 Hessian2Input。

 

了解了 Dubbo Serialize 层的核心接口以及 Hessian2 序列化算法的接入方式之后,其他序列化算法对应模块也类似。

 

参考: https://javaedge.blog.csdn.net/article/details/109064680

标签:分析,Dubbo,Java,对象,Serialize,接口,序列化,public
From: https://blog.51cto.com/u_14014612/5769007

相关文章

  • Kaggle竞赛神器—Facets:快速评估数据集质量,把控数据分析核心环节
    在机器学习任务中,数据集的质量优劣对数据分析的结果影响非常大,所谓Garbagein,garbageout,数据决定模型的上限,因此数据质量成为数据分析流程不可或缺的一个环节。即使是像K......
  • Dubbo——Remoting 层核心接口分析
    前言dubbo-remoting模块提供了多种客户端和服务端通信的功能。在Dubbo的整体架构设计图中,我们可以看到最底层红色框选中的部分即为Remoting层,其中包括了Exchange、Tr......
  • Dubbo——Buffer 缓冲区
    前言Buffer是一种字节容器,在Netty等NIO框架中都有类似的设计,例如,JavaNIO中的ByteBuffer、Netty4中的ByteBuf。Dubbo抽象出了ChannelBuffer接口对底层NIO框架......
  • 如何合理的配置HTTP代理进行大数据分析
    互联网营销战要抢占先机,数据是企业的核心资产。通过数据分析,我们可以从中探索出一条新的盈利道路,一条符合用户需求的道路。而在HTTP代理的介入之下,数据挖掘已经不再是......
  • java 内存分析优化
    MATjava内存分析工具:https://cloud.tencent.com/developer/article/1377476内存溢出问题排查:https://mp.weixin.qq.com/s/lQut5nWIT3WbuVA57bw4pw......
  • Innodb对表加锁的过程分析
    本节我们通过一些具体的案例来分析Innodb对表上锁的过程。具体场景如下图所示。在这里我们将语句分为4类:普通select(快照读)、锁定读、半一致性读和insert语句。  ......
  • 网游帧同步的分析与设计
    本文是我20年春节期间写的,最初发表在了知乎上,本wiki中省略掉了前面的吹水环节,直接开始讲干的,内容是直接从网页粘贴过来的,有些格式不太友好,如有兴趣可以查看原文,原文链接:htt......
  • 04.大型数据库应用技术课堂测试05-日志数据分析-错误总结
    错误总结:1.ExpressionnotinGROUPBYkey'id'解决:在groupby子句中,select查询的列,要么需要是groupby中的列,要么得是用聚合函数(比如sum、count等)加工过的列。不......
  • Go素数筛选分析
    Go素数筛选分析1.素数筛选介绍学习Go语言的过程中,遇到素数筛选的问题。这是一个经典的并发编程问题,是某大佬的代码,短短几行代码就实现了素数筛选。但是自己看完原理和代......
  • 小米运动健康及数据库分析
    数据库分析对部分数据库中的数据进行分析device_db在device表中,会储存配对的设备的信息如上图所示,连接的设备为Redmi手表2,它的设备id是513932244,mac地址44:27:F3:......