第03篇: 手写JavaRPC框架之搞定序列化

举报
西魏陶渊明 发表于 2022/09/25 03:20:58 2022/09/25
【摘要】 作者: 西魏陶渊明 博客: https://blog.springlearn.cn/ 天下代码一大抄, 抄来抄去有提高, 看你会抄不会抄! 一、前言 天下代码一大抄, 抄来抄去有提...

作者: 西魏陶渊明
博客: https://blog.springlearn.cn/

天下代码一大抄, 抄来抄去有提高, 看你会抄不会抄!

一、前言

天下代码一大抄, 抄来抄去有提高, 看你会抄不会抄!从本篇开始后面的所有章节都是实战环节,每节一个小目标,最终我们实现完整的JavaRPC的框架,然后发布maven仓库,感兴趣的同学可以下载研究。大家如果想要获取源码的话可以私信: RPC,自动回复仓库地址。

其实这些东西并没有什么难度,只要从头到尾跟着我们一起coding,其实就会发现不过如此。所以就算是新手也不要有心里负担。还是那句话: “天下代码一大抄, 抄来抄去有提高, 看你会抄不会抄”。主要的是思想,而不是死记硬背。所以我们来主要来学习设计思想,具体的代码,收藏用的时候看下就好了。

下面我们废话就不多少,直接开始吧。

二、目标

2.1 目标介绍

RPC框架中最基础的一个功能就是通讯,而通讯的本质就是发送方将数据转换成二进制的数据流,然后通过网络管道将数据发送给服务方,服务方在通过读取管道中的二进制数据最终将数据从二进制转换成Java对象,然后供Java系统处理,处理完成后再将Java结果对象转换成二进制数据通过管道发送回去。

这里有两个重点,第一个是网络通信,就是图中的网路连接管道,第二个是管道中的二进制数据。本篇文章我们先研究后者,就是通过代码将Java对象转换成二进制数据。因为通信比较难,内容也较多,所以我们先易后难,难的放到下一篇文章在说。

这里有两个术语: 序列化和反序列化

如下实例User类,转换成二进制数据就是一个数组。这个Java对象转换二进制数据的过程叫做序列化。
而二进制数据转换成Java对象的过程叫做反序列化。

2.2 两个小目标

  • 第一个目标就是我们实现多种序列化的能力。
  • 第二个目标是来选择一个最优的序列化方案。

为什么说要选择最优的序列化方案呢? 因为我们要适配下面这两种场景。

  1. 第一种是对性能要有比较高的,这种情况就要求我们序列化和反序列的速度要足够快
  2. 第二种就是对内存和空间要有比较高的,就要求我们的序列化后的数据要足够的小。

下面就开始Coding了。

三、设计

3.1 工程结构

首先我们根据上一篇文章中定义的项目分层结构,先把需要的所有层给创建出来。然后实现序列化。

mojito/mojito-net/src/main/java/cn/lxchinesszz/mojito on  master [!+?] 
.
├── api
├── business
├── codec
├── exception
│   ├── BusinessServerHandlerException.java
│   ├── DeserializeException.java
│   ├── HttpsTokenFileException.java
│   ├── ProtocolException.java
│   ├── RemotingException.java
│   ├── SerializeException.java
│   └── SignatureException.java
├── exchange
└── serialize
    ├── AbstractSerializer.java
    ├── Serializer.java
    └── impl
        ├── Hession2ObjectSerializer.java
        ├── HessionObjectSerializer.java
        ├── NettyCompactObjectSerializer.java
        ├── NettyObjectSerializer.java
        └── ProtostuffObjectSerializer.java


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

3.2 代码结构

首先我们先定义接口,为什么定义接口呢? 方便后面的扩展。
接口也非常的简单就三个方法。

  1. 负责将任意对象转换成二进制数组
  2. 将二进制数组转成Object对象
  3. 将二进制数组转换成指定的Java对象

UML图设计

3.3 实现

  • ProtostuffObjectSerializer
    使用谷歌开源的序列化库,特点占用极小。
public class ProtostuffObjectSerializer extends AbstractSerializer {

    /**
     * 线程数会有限制,不会无穷大的使用
     */
    private static final ThreadLocal<LinkedBuffer> BUFFER = InheritableThreadLocal.withInitial(() ->
            LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));

    @Override
    @SuppressWarnings("unchecked")
    public byte[] doSerialize(Object dataObject) throws SerializeException {
        // // RuntimeSchema 懒加载内置缓存,所以我们不用在缓存了
        Schema<Object> schema = (Schema<Object>) RuntimeSchema.getSchema(dataObject.getClass());
        LinkedBuffer linkedBuffer = BUFFER.get();
        byte[] bytes = ProtostuffIOUtil.toByteArray(dataObject, schema, linkedBuffer);
        linkedBuffer.clear();
        return bytes;
    }

    @Override
    public Object doDeserialize(byte[] data) throws DeserializeException {
        throw new UnsupportedOperationException(getClass() + "必须指定反序列化类型");
    }

    @Override
    public <T> T deserialize(byte[] data, Class<T> dataType) throws DeserializeException {
        return schema(data, dataType);
    }

    public <T> T schema(byte[] data, Class<T> dataType) throws DeserializeException {
        try {
            Schema<T> schema = RuntimeSchema.getSchema(dataType);
            T dataTypeObj = dataType.newInstance();
            ProtostuffIOUtil.mergeFrom(data, dataTypeObj, schema);
            return dataTypeObj;
        } catch (InstantiationException | IllegalAccessException e) {
            throw new DeserializeException(e);
        }
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • NettyCompactObjectSerializer
    Netty原生支持的序列化协议
public class NettyCompactObjectSerializer extends AbstractSerializer {

    @Override
    public byte[] doSerialize(Object dataObject) throws SerializeException {
        try (ByteArrayOutputStream dataArr = new ByteArrayOutputStream();
             CompactObjectOutputStream oeo = new CompactObjectOutputStream(dataArr)) {
            oeo.writeObject(dataObject);
            oeo.flush();
            return dataArr.toByteArray();
        } catch (IOException e) {
            throw new SerializeException(e);
        }
    }

    @Override
    public Object doDeserialize(byte[] data) throws DeserializeException {
        Object o;
        try (CompactObjectInputStream odi = new CompactObjectInputStream(new ByteArrayInputStream(data), ClassResolvers.cacheDisabled(null))) {
            o = odi.readObject();
        } catch (ClassNotFoundException | IOException e) {
            throw new DeserializeException(e);
        }
        return o;
    }
    
    private static class CompactObjectOutputStream extends ObjectOutputStream {

        static final int TYPE_FAT_DESCRIPTOR = 0;
        static final int TYPE_THIN_DESCRIPTOR = 1;

        CompactObjectOutputStream(OutputStream out) throws IOException {
            super(out);
        }

        @Override
        protected void writeStreamHeader() throws IOException {
            writeByte(STREAM_VERSION);
        }

        @Override
        protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException {
            Class<?> clazz = desc.forClass();
            if (clazz.isPrimitive() || clazz.isArray() || clazz.isInterface() ||
                    desc.getSerialVersionUID() == 0) {
                write(TYPE_FAT_DESCRIPTOR);
                super.writeClassDescriptor(desc);
            } else {
                write(TYPE_THIN_DESCRIPTOR);
                writeUTF(desc.getName());
            }
        }
    }

    /**
     * 压缩
     */
    private static class CompactObjectInputStream extends ObjectInputStream {

        static final int TYPE_FAT_DESCRIPTOR = 0;

        static final int TYPE_THIN_DESCRIPTOR = 1;

        private final ClassResolver classResolver;

        CompactObjectInputStream(InputStream in, ClassResolver classResolver) throws IOException {
            super(in);
            this.classResolver = classResolver;
        }

        @Override
        protected void readStreamHeader() throws IOException {
            int version = readByte() & 0xFF;
            if (version != STREAM_VERSION) {
                throw new StreamCorruptedException(
                        "Unsupported version: " + version);
            }
        }

        @Override
        protected ObjectStreamClass readClassDescriptor()
                throws IOException, ClassNotFoundException {
            int type = read();
            if (type < 0) {
                throw new EOFException();
            }
            switch (type) {
                case TYPE_FAT_DESCRIPTOR:
                    return super.readClassDescriptor();
                case TYPE_THIN_DESCRIPTOR:
                    String className = readUTF();
                    Class<?> clazz = classResolver.resolve(className);
                    return ObjectStreamClass.lookupAny(clazz);
                default:
                    throw new StreamCorruptedException(
                            "Unexpected class descriptor type: " + type);
            }
        }

        @Override
        protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
            Class<?> clazz;
            try {
                clazz = classResolver.resolve(desc.getName());
            } catch (ClassNotFoundException ignored) {
                clazz = super.resolveClass(desc);
            }

            return clazz;
        }
    }
}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • HessionObjectSerializer
    基于Hession序列化协议的封装
public class HessionObjectSerializer extends AbstractSerializer {

    @Override
    public byte[] doSerialize(Object dataObject) throws SerializeException {
        HessianOutput oeo = null;
        try (ByteArrayOutputStream dataArr = new ByteArrayOutputStream()) {
            oeo = new HessianOutput(dataArr);
            oeo.writeObject(dataObject);
            oeo.flush();
            return dataArr.toByteArray();
        } catch (IOException e) {
            throw new SerializeException(e);
        } finally {
            if (oeo != null) {
                try {
                    oeo.close();
                } catch (IOException e) {
                    throw new SerializeException(e);
                }
            }
        }
    }

    @Override
    public Object doDeserialize(byte[] data) throws DeserializeException {
        Object o;
        HessianInput odi = new HessianInput(new ByteArrayInputStream(data));
        try {
            o = odi.readObject();
        } catch (IOException e) {
            throw new DeserializeException(e);
        }
        return o;
    }

}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • Hession2ObjectSerializer
    对Hession2序列化的封装
public class Hession2ObjectSerializer extends AbstractSerializer {

    @Override
    public byte[] doSerialize(Object dataObject) throws SerializeException {
        ByteArrayOutputStream dataArr = new ByteArrayOutputStream();
        Hessian2Output oeo = null;
        try {
            oeo = new Hessian2Output(dataArr);
            oeo.writeObject(dataObject);
            oeo.flush();
        } catch (IOException e) {
            throw new SerializeException(e);
        } finally {
            if (oeo != null) {
                try {
                    oeo.close();
                } catch (IOException e) {
                    throw new SerializeException(e);
                }
            }
        }
        return dataArr.toByteArray();
    }

    @Override
    public Object doDeserialize(byte[] data) throws DeserializeException {
        Object o;
        Hessian2Input odi = new Hessian2Input(new ByteArrayInputStream(data));
        try {
            o = odi.readObject();
        } catch (IOException e) {
            throw new DeserializeException(e);
        }
        return o;
    }
    
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

如果有优化的建议,希望多多评论,指点一二。以上代码已上传,代码较多就不这里展示。感兴趣的同学可以访问下面链接,查看代码。

仓库实现

四、性能分析

4.1 性能分析

那么以上这几种序列化协议那种速度最好呢?

首先我们先看序列化的速度对比。

使用20个线程,预热3秒,执行5秒对比下吞吐量及内存使用。这里我们只截图排名第一的实现。具体的对比数据已放在github上面,感兴趣的可以自己拉下来看下。

  • ProtostuffObjectSerializer 吞吐量 10,774,518 / s
  • Hession2ObjectSerializer 吞吐量 3,200,559 / s
  • HessionObjectSerializer 吞吐量 3,307,595 / s
  • NettyCompactObjectSerializer 吞吐量 874,793 / s

从上面中的数据我们能看出Protostuff是遥遥领先,这得益于提前把数据类型,提前进行了处理并缓存,其次是友好的API涉及,可以让我们避免频繁生成堆空间。

更多详细的数据对比,可以点下面链接查看。

详细数据

4.2 序列化长度

前面Protostuff在性能上是遥遥领先,那么在数据压缩比例上究竟谁能胜出呢? 相对于前面性能测试,这种大小测试是比较容易测试, 我们直接执行下面代码,查看结果。

    @Test
    @DisplayName("序列化数据大小对比")
    public void test7() {
        ProtostuffObjectSerializer nettyObjectSerializer = new ProtostuffObjectSerializer();
        byte[] jay = nettyObjectSerializer.serialize(new UserSerializable("周杰伦", 42));
        ColorConsole.colorPrintln("{},序列化数据长度:{}", "Protostuff", jay.length);

        ColorConsole.colorPrintln("{},序列化数据长度:{}", "Hession2",
                new Hession2ObjectSerializer().serialize(new UserSerializable("周杰伦", 42)).length);

        ColorConsole.colorPrintln("{},序列化数据长度:{}", "Hession",
                new HessionObjectSerializer().serialize(new UserSerializable("周杰伦", 42)).length);

        ColorConsole.colorPrintln("{},序列化数据长度:{}", "NettyCompact",
                new NettyCompactObjectSerializer().serialize(new UserSerializable("周杰伦", 42)).length);
    }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
实现类 长度
ProtostuffObjectSerializer 13字节
Hession2ObjectSerializer 88字节
HessionObjectSerializer 98字节
NettyCompactObjectSerializer 132字节

还是Protostuff大幅度领先,那么我们的默认序列化协议就使用了吧。是不是很简单呢?

五、总结

通过这一节的实现,我们已经可以使用序列化工具,将Java对象转换成二进制数据了,那么下一篇我们就来实现如果创建网络连接管道吧。

那么你准备好跟我一起Coding了吗?

文章来源: springlearn.blog.csdn.net,作者:西魏陶渊明,版权归原作者所有,如需转载,请联系作者。

原文链接:springlearn.blog.csdn.net/article/details/126151001

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。