手写RPC框架之序列化协议选择

本文介绍了序列化的基本概念,探讨了Java自带序列化、Kryo、Protobuf和Protostuff四种序列化协议的优缺点。重点讨论了Kryo的高性能和语言限制,Protobuf的高效与兼容性,以及Protostuff作为 Protobuf的简化Java实现。在自定义RPC框架中,作者实现了Kryo和Protostuff两种序列化方式。
摘要由CSDN通过智能技术生成

本文部分内容参考JavaGuide

序列化相关概念介绍

本文主要提到的都是基于二进制的序列化协议,像 JSONXML这种属于文本类序列化方式。虽然 JSONXML可读性比较好,但是性能较差,一般不会选择。

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

  • 序列化: 将数据结构或对象转换成二进制字节流的过程
  • 反序列化:将在序列化过程中所生成的二进制字节流的过程转换成数据结构或者对象的过程

dubbo中的序列化方式介绍在Dubbo中使用高效的Java序列化(Kryo和FST)
在这里插入图片描述

JDK自带的序列化方式

public class TestClass implements Serializable {
    private static final long serialVersionUID = 1905122041950251207L;
}

它的作用就是在Java进行序列化工作时,会将serialVersionUID 与所要序列化的目标一起序列化,这样一来,在反序列化的过程中会使用被序列化的serialVersionUID与类中的serialVersionUID对比,如果两者相等,则反序列化成功,否则,反序列化失败,会抛出 InvalidClassException 异常。强烈推荐每个序列化类都手动指定其 serialVersionUID,如果不手动指定,那么编译器会动态生成默认的序列化号

我们在生产环境中几乎很少使用该种序列化方式,原因是:

  • 无法跨语言
  • 性能差,序列化速度慢,序列化后的传输数据体积大
  • 部分版本有安全漏洞

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

Kryo序列化效率很高,但是只兼容 Java语言,dubbo也在逐步用Kryo取代hessian。

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

了解Kryo变长存储类注册等请参考

Protobuf
Protobuf 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。

简单来讲, Protobuf 是结构数据序列化方法,可简单类比于 XML,其具有以下特点:

  • 语言无关、平台无关。即 ProtoBuf 支持 Java、C++、Python 等多种语言,支持多个平台
  • 高效。即比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单
  • 扩展性、兼容性好。你可以更新数据结构,而不影响和破坏原有的旧程序

但是,缺点就是在使用中过于繁琐,因为你需要自己定义 IDL 文件和生成对应的序列化代码。这样虽然不然灵活,但是,另一方面导致protobuf没有序列化漏洞的风险。

Protobuf包含序列化格式的定义、各种语言的库以及一个IDL编译器。正常情况下你需要定义proto文件,然后使用IDL编译器编译成你需要的语言

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

Protostuff
protostuff 是基于Google protobuf 实现的一个Java 库,专门针对Java的序列化,提供了更多的功能和更简易的用法。虽然更加易用,但是不代表 ProtoStuff 性能更差。

Github上原话介绍
在这里插入图片描述

序列化实现

my-rpc框架定制了kryoprotostuff两种序列化实现。

接口定义

@SPI  // 表名可通过Extensionloader加载
public interface Serializer {
    /**
     * 序列化
     * @param obj
     * @return
     */
    byte[] serialize(Object obj);

    /**
     * 反序列化
     *
     * @param bytes 序列化后的字节数组
     * @param clazz 目标类
     * @param <T>   类的类型。举个例子,  {@code String.class} 的类型是 {@code Class<String>}.
     *              如果不知道类的类型的话,使用 {@code Class<?>}
     * @return 反序列化的对象
     */
    <T> T deserialize(byte[] bytes, Class<T> clazz);
}

Kryo

public class KryoSerializer implements Serializer {
    // Kryo is not thread safe. Each thread should have its own Kryo, Input, and Output instances.
    private final ThreadLocal<Kryo> kryoThreadLocal = new ThreadLocal<Kryo>(){
        @Override
        protected Kryo initialValue() {
            Kryo kryo = new Kryo();
            kryo.setRegistrationRequired(false);
            return kryo;
        }
    };

    @Override
    public byte[] serialize(Object obj) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        Output output = new Output(byteArrayOutputStream);

        Kryo kryo = kryoThreadLocal.get();
        kryo.writeObject(output, obj);

        // 如果我们使用完ThreadLocal对象而没有手动删掉,
        // 那么后面的请求就有机会使用到被使用过的ThreadLocal对象
        kryoThreadLocal.remove();
        return output.toBytes();
    }

    @Override
    public <T> T deserialize(byte[] bytes, Class<T> clazz) {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        Input input = new Input(byteArrayInputStream);
        Kryo kryo = kryoThreadLocal.get();
        Object obj = kryo.readObject(input, clazz);
        kryoThreadLocal.remove();
        return clazz.cast(obj);
    }
}

Protostuff

public class ProtostuffSerializer implements Serializer {
    @Override
    public byte[] serialize(Object obj) {
        Schema schema = RuntimeSchema.createFrom(obj.getClass());
        LinkedBuffer linkedBuffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
        byte[] res = ProtostuffIOUtil.toByteArray(obj, schema, linkedBuffer);
        return res;
    }

    @Override
    public <T> T deserialize(byte[] bytes, Class<T> clazz) {
        Schema<T> schema = RuntimeSchema.getSchema(clazz);
        T obj = schema.newMessage();
        ProtostuffIOUtil.mergeFrom(bytes, obj, schema);
        return obj;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值