序列化介绍 及 常见序列化协议(JDKKryoProtobufHessian)解析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了序列化介绍 及 常见序列化协议(JDKKryoProtobufHessian)解析相关的知识,希望对你有一定的参考价值。

(目录)


序列化介绍以及序列化协议选择

什么是序列化和反序列化?

如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。

简单来说:

对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class),但是在 C++这种半面向对象的语言中,struct(结构体)定义的是数据结构类型,而 class 对应的是对象类型。


序列化和反序列化常见应用场景

下面是序列化和反序列化常见应用场景:

维基百科是如是介绍序列化的:

综上:序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中


序列化协议对应于 TCP/IP 4 层模型的哪一层?

我们知道网络通信的双方必须要采用和遵守相同的协议。

TCP/IP 四层模型是下面这样的,序列化协议属于哪一层呢?

如上图所示,OSI 七层协议模型中:


常见序列化协议有哪些?

JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且存在安全问题

比较常用的序列化协议有


① JDK 序列化

JDK 自带的序列化,只需实现 java.io.Serializable接口即可。

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
@ToString
public class RpcRequest implements Serializable 
    private static final long serialVersionUID = 1905122041950251207L;
    private String requestId;
    private String interfaceName;
    private String methodName;
    private Object[] parameters;
    private Class<?>[] paramTypes;
    private RpcMessageTypeEnum rpcMessageTypeEnum;


serialVersionUID 有什么作用?

序列化号 serialVersionUID 属于版本控制的作用

反序列化时,会检查 serialVersionUID 是否和当前类的 serialVersionUID 一致。

如果 serialVersionUID 不一致则会抛出 InvalidClassException 异常。

强烈推荐每个序列化类都手动指定其 serialVersionUID,如果不手动指定,那么编译器会动态生成默认的 serialVersionUID


serialVersionUID 不是被 static 变量修饰了吗?为什么还会被“序列化”?

static 修饰的变量是静态变量,位于方法区,本身是不会被序列化的。

static 变量是属于类的而不是对象。你反序列之后,static 变量的值就像是默认赋予给了对象一样,看着就像是 static 变量被序列化,实际只是假象罢了。

注意:


如果有些字段不想进行序列化怎么办?

对于不想进行序列化的变量,可以使用 transient 关键字修饰

transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。

关于 transient 还有几点注意:


为什么不推荐使用 JDK 自带的序列化?

我们很少或者说几乎不会直接使用 JDK 自带的序列化方式,主要原因有下面这些原因


② Kryo

Kryo 是一个高性能的序列化/反序列化工具,由于其变长存储特性并使用了字节码生成机制,拥有较高的运行速度和较小的字节码体积

另外,Kryo 已经是一种非常成熟的序列化实现了,已经在 Twitter、Groupon、Yahoo 以及多个著名开源项目(如 Hive、Storm)中广泛的使用。

/**
 * Kryo serialization class, Kryo serialization efficiency is very high, but only compatible with Java language
 *
 * @author shuang.kou
 * @createTime 2020年05月13日 19:29:00
 */
@Slf4j
public class KryoSerializer implements Serializer 

    /**
     * Because Kryo is not thread safe. So, use ThreadLocal to store Kryo objects
     */
    private final ThreadLocal<Kryo> kryoThreadLocal = ThreadLocal.withInitial(() -> 
        Kryo kryo = new Kryo();
        kryo.register(RpcResponse.class);
        kryo.register(RpcRequest.class);
        return kryo;
    );

    @Override
    public byte[] serialize(Object obj) 
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
             Output output = new Output(byteArrayOutputStream)) 
            Kryo kryo = kryoThreadLocal.get();
            // Object->byte:将对象序列化为byte数组
            kryo.writeObject(output, obj);
            kryoThreadLocal.remove();
            return output.toBytes();
         catch (Exception e) 
            throw new SerializeException("Serialization failed");
        
    

    @Override
    public <T> T deserialize(byte[] bytes, Class<T> clazz) 
        try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
             Input input = new Input(byteArrayInputStream)) 
            Kryo kryo = kryoThreadLocal.get();
            // byte->Object:从byte数组中反序列化出对象
            Object o = kryo.readObject(input, clazz);
            kryoThreadLocal.remove();
            return clazz.cast(o);
         catch (Exception e) 
            throw new SerializeException("Deserialization failed");
        
    


Github 地址:https://github.com/EsotericSoftware/kryo 。


③ Protobuf

Protobuf 出自于 Google,性能还比较优秀,也支持多种语言,同时还是跨平台的。就是在使用中过于繁琐,因为你需要自己定义 IDL 文件和生成对应的序列化代码。这样虽然不灵活,但是,另一方面导致 protobuf 没有序列化漏洞的风险。

一个简单的 proto 文件如下:

// protobuf的版本
syntax = "proto3";
// SearchRequest会被编译成不同的编程语言的相应对象,比如Java中的class、Go中的struct
message Person 
  //string类型字段
  string name = 1;
  // int 类型字段
  int32 age = 2;

Github 地址:https://github.com/protocolbuffers/protobuf。


④ ProtoStuff

由于 Protobuf 的易用性,它的哥哥 Protostuff 诞生了。

protostuff 基于 Google protobuf,但是提供了更多的功能和更简易的用法。虽然更加易用,但是不代表 ProtoStuff 性能更差

Github 地址:https://github.com/protostuff/protostuff。


⑤ Hessian

Hessian 是一个轻量级的,自定义描述的二进制 RPC 协议

Hessian 是一个比较老的序列化实现了,并且同样也是跨语言的。

Dubbo2.x 默认启用的序列化方式是 Hessian2 ,但是,Dubbo 对 Hessian2 进行了修改,不过大体结构还是差不多。


总结

Kryo 是专门针对 Java 语言序列化方式并且性能非常好,如果你的应用是专门针对 Java 语言的话可以考虑使用,并且 Dubbo 官网的一篇文章中提到说推荐使用 Kryo 作为生产环境的序列化方式。(文章地址:https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/rest/)

像 Protobuf、 ProtoStuff、hessian 这类都是跨语言的序列化方式,如果有跨语言需求的话可以考虑使用。


以上是关于序列化介绍 及 常见序列化协议(JDKKryoProtobufHessian)解析的主要内容,如果未能解决你的问题,请参考以下文章

常见远程调用协议

需求预测介绍及时间序列常用方法培训

Dubbo 协议

服务端(后端)| Thrift序列化的核心思想

java中常见的rpc框架介绍

通讯协议及Google.Protobuf生成c#代码 序列及反序列化