鱼皮项目简易版 RPC 框架开发(三)

本文为笔者阅读鱼皮的项目 《简易版 RPC 框架开发》的笔记,如果有时间可以直接去看原文,

1. 简易版 RPC 框架开发

前面的内容可以笔者的前面两个篇笔记

鱼皮项目简易版 RPC 框架开发(一)

鱼皮项目简易版 RPC 框架开发(二)

引用:

1. 简易版 RPC 框架开发

鱼皮项目简易版 RPC 框架开发(一)

鱼皮项目简易版 RPC 框架开发(二)

RPC框架的简单理解

ByteArrayOutputStream详解

Java中ObjectOutputStream和ObjectInputStream的基本使用详解

ByteArrayInputStream 类详解

对象的反序列化流ObjectInputStream

在分布式系统中,RPC(远程过程调用)框架的核心挑战之一是如何高效地在网络间传输对象数据。序列化器模块正是解决这一问题的关键组件。本文将深入解析一个RPC框架中的序列化器实现,揭示其设计哲学与技术细节。

代码

Serializer接口

java 复制代码
package com.yupi.yurpc.serializer;

import java.io.IOException;

/**
 * 序列化器接口
 */
public interface Serializer {

    /**
     * 序列化
     *
     * @param object
     * @param <T>
     * @return
     * @throws IOException
     */
    <T> byte[] serialize(T object) throws IOException;

    /**
     * 反序列化
     *
     * @param bytes
     * @param type
     * @param <T>
     * @return
     * @throws IOException
     */
    <T> T deserialize(byte[] bytes, Class<T> type) throws IOException;
}

JdkSerializer类

java 复制代码
package com.yupi.yurpc.serializer;

import java.io.*;

/**
 * JDK 序列化器
 */
public class JdkSerializer implements Serializer {

    /**
     * 序列化
     *
     * @param object
     * @param <T>
     * @return
     * @throws IOException
     */
    @Override
    public <T> byte[] serialize(T object) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream)) {
            objectOutputStream.writeObject(object);
            return outputStream.toByteArray();
        }
    }

    /**
     * 反序列化
     *
     * @param bytes
     * @param type
     * @param <T>
     * @return
     * @throws IOException
     */
    @Override
    public <T> T deserialize(byte[] bytes, Class<T> type) throws IOException {
        ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        try {
            return (T) objectInputStream.readObject();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            objectInputStream.close();
        }
    }
}

一、序列化器:分布式通信的基石

序列化器在RPC框架中扮演着数据格式转换器的角色,主要职责包括:

  • 序列化:将内存中的对象转换为字节流

  • 反序列化:将接收的字节流还原为可操作的对象

  • 跨语言支持:实现不同语言间的数据交换(可选)

  • 性能优化:平衡序列化速度与数据大小

二、抽象接口设计:策略模式的完美实践

`Serializer.java` 文件定义了序列化器的抽象接口:

public interface Serializer {

<T> byte[] serialize(T object) throws IOException;

<T> T deserialize(byte[] bytes, Class<T> type) throws IOException;

}

设计亮点:

  1. 泛型支持:使用`<T>`泛型确保类型安全

  2. 异常透明:明确声明`IOException`让调用方处理异常

  3. 简洁契约:仅定义两个核心方法,符合接口隔离原则

  4. 策略模式:为不同序列化算法提供统一接入点

这种接口设计使得我们可以轻松扩展各种序列化实现(JSON、Protobuf、Hessian等),而无需修改框架核心代码。

三、JDK实现:Java原生序列化解析

`JdkSerializer.java` 提供了基于Java原生序列化的实现:

// 序列化实现

public <T> byte[] serialize(T object) throws IOException {

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream)) {

objectOutputStream.writeObject(object);

return outputStream.toByteArray();

}

}

// 反序列化实现

public <T> T deserialize(byte[] bytes, Class<T> type) throws IOException {

try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);

ObjectInputStream objectInputStream = new ObjectInputStream(inputStream)) {

return (T) objectInputStream.readObject();

} catch (ClassNotFoundException e) {

throw new RuntimeException("类未找到: " + e.getMessage());

}

}

关键技术点:

  1. 内存流优化
  • 使用`ByteArrayOutputStream`避免磁盘IO

  • 字节数组操作大幅提升性能

  1. 资源安全管理
  • 序列化使用try-with-resources自动关闭资源

  • 反序列化优化后同样采用自动关闭

  1. 异常处理策略
  • 检查异常`IOException`传递给调用方

  • `ClassNotFoundException`转换为运行时异常

  • 添加明确错误信息便于问题定位

  1. 类型转换安全
  • 基于传入的`Class<T> type`进行类型校验

  • 类型转换前确保对象兼容性

四、序列化器设计进阶思考

  1. 性能优化方向

// 示例:添加缓存优化

private final Map<Class<?>, SoftReference<byte[]>> serializationCache =

new ConcurrentHashMap<>();

  1. 安全增强方案

// 示例:限制反序列化类

objectInputStream.setObjectInputFilter(filterInfo ->

allowedClasses.contains(filterInfo.serialClass()) ?

ObjectInputFilter.Status.ALLOWED :

ObjectInputFilter.Status.REJECTED);

  1. 扩展性设计

// 示例:支持多种序列化协议

public enum SerializerAlgorithm {

JDK((byte)1), JSON((byte)2), PROTOBUF((byte)3);

public static Serializer getByCode(byte code) {

// 根据编码返回对应序列化器

}

}

五、序列化技术选型对比

| 特性 | JDK序列化 | JSON | Protobuf |

|---------------|------------|------------|------------|

| 跨语言支持 | ❌ | ✅ | ✅ |

| 数据大小 | 大 | 中 | 小 |

| 性能 | 低 | 中 | 高 |

| 可读性 | ❌ | ✅ | ❌ |

| 开发便利性 | ✅ | ✅ | 中 |

选型建议:内部系统可优先考虑JDK序列化;跨语言场景推荐Protobuf;调试阶段可使用JSON

六、最佳实践总结

  1. 接口隔离原则:保持序列化接口简洁明确

  2. 资源安全:始终确保流的正确关闭

  3. 类型安全:在反序列化时验证类型信息

  4. 异常分层:区分可恢复异常与系统级错误

  5. 可扩展设计:预留协议升级和算法替换能力

  6. 安全防护:对反序列化操作施加白名单限制

序列化器作为RPC框架的通信基石,其设计质量直接影响整个系统的性能和可靠性。通过清晰的接口定义和严谨的实现细节,我们为构建高性能分布式系统奠定了坚实基础。

补充

ByteArrayOutputStream

ByteArrayOutputStream 对byte类型数据进行写入的类 相当于一个中间缓冲层,将类写入到文件等其他outputStream。它是对字节进行操作,属于内存操作流

ByteArrayOutputStream继承了OutputStream

ByteArrayOutputStream类中的成员和方法的介绍:

复制代码
protected byte buf[];
//数据存储的地方
protected int count;
//计数器  表示数据的个数

ByteArrayOutputStream的构造方法有两个;

java 复制代码
//创建一个新的 byte 数组输出流。缓冲区的容量最初是 32 字节,如有必要可增加其大小
  public ByteArrayOutputStream() {
        this(32);
    }
    //创建一个新的 byte 数组输出流,它具有指定大小的缓冲区容量(以字节为单位)
  public ByteArrayOutputStream(int size) {
       if (size < 0) {
            throw new IllegalArgumentException("Negative initial size: "
                                               + size);
        }
        buf = new byte[size];
    }

而ByteArrayOutputStream中有三个write()方法:

java 复制代码
//将指定的int类型的数据写入此 byte 数组输出流
public  void write(int b){
        ensureCapacity(count + 1);
        buf[count] = (byte) b;
        count += 1;
}
 
/**将指定 byte 数组中从偏移量 
    off 开始的 
    len 个字节写入此 byte 数组输出流。*/
public  void write(byte b[], int off, int len){
        if ((off < 0) || (off > b.length) || (len < 0) ||
            ((off + len) - b.length > 0)) {
            throw new IndexOutOfBoundsException();
        }
        ensureCapacity(count + len);
        System.arraycopy(b, off, buf, count, len);
        count += len;
}

toByteArray()方法

java 复制代码
//创建一个新分配的 byte 数组。其大小是此输出流的当前大小,并且缓冲区的有效内容已复制到该数组中。
    public synchronized byte toByteArray()[] {
        return Arrays.copyOf(buf, count);
    }

ObjectOutputStream

ObjectOutputStream是一个高级流, 将 Java 对象的基本数据类型和图形写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。

注意:只能将支持 java.io.Serializable 接口的对象写入流中。每个 serializable 对象的类都被编码,编码内容包括类名和类签名、对象的字段值和数组值,以及从初始对象中引用的其他所有对象的闭包。

java 复制代码
构造函数
//为完全重新实现 ObjectOutputStream 的子类提供一种方法,让它不必分配仅由 ObjectOutputStream 的实现使用的私有数据。
protected ObjectOutputStream();



//创建写入指定 OutputStream 的 ObjectOutputStream。此构造方法将序列化流部分写入底层流;调用者可以通过立即刷新流,确保在读取头部时,用于接收 ObjectInputStreams 构造方法不会阻塞。
public ObjectOutputStream(OutputStream out);

常用方法

//将指定的对象写入 ObjectOutputStream。对象的类、类的签名,以及类及其所有超类型的非瞬态和非静态字段的值都将被写入。

java 复制代码
public final void writeObject(Object obj);

这两个的input对应的是他们的输入方法

ByteArrayInputStream 类详解

ByteArrayInputStream 是 Java 中用于从字节数组读取数据输入流,位于 java.io 包。它允许将内存中的字节数组当作输入流来读取,是处理内存数据的常用工具。

  1. 核心特性

内存数据源:从字节数组(byte[])读取数据

无需关闭:close() 方法为空操作(无系统资源需要释放)

线程不安全:多线程访问需外部同步

支持标记/重置:可重复读取数据(mark() 和 reset())

  1. 类继承关系

  2. 构造方法

构造方法 说明

ByteArrayInputStream(byte[] buf) 使用整个字节数组作为数据源

ByteArrayInputStream(byte[] buf, int offset, int length) 使用数组的指定区间

  1. 核心方法

(1)读取数据

方法 说明

int read() 读取单个字节(返回0-255,-1表示结束)

int read(byte[] b, int off, int len) 读取数据到字节数组

long skip(long n) 跳过指定字节数

ObjectInputStream

该流位于API中java,io.ObjectInputStream,作用是将文件中的对象,反序列化为,以流的方式读取出来

ObjectInputStream中的构造方法

ObjectInputStream(InputStream in)创建从指定 InputStream 读取的 ObjectInputStream
ObjectInputStream中特有的成员方法

Object readObject()从 ObjectInputStream 读取对象
ObjectInputStream的使用步骤

创建ObjectInputStream对象,构造方法中传递字节输入流

使用ObjectInputStream对象中的readObject方法读取文件中的对象

释放资源

使用读取出来的对象