前言

最近不小心被隔离,放假思考一番,决定开始在手写序列。这个序列在之前看Nacous和网关源码的时候就有想法,只是一直没落实下来,趁着隔离行动起来。

必备知识介绍

序列化与反序列化

序列化是把对象的状态信息转化为可存储或传输的形式过程,也就是把对象转化为字节序列的过程称为对象的序列化;

反序列化是序列化的逆向过程,把字节数组反序列化为对象,把字节序列恢复为对象的过程成为对象的反序列化;

在Java中通过 JDK 提供了 Java 对象的序列化方式实现对象序列化传输,主要通过输出流java.io.ObjectOutputStream和对象输入流java.io.ObjectInputStream来实现;

java.io.ObjectOutputStream:表示对象输出流 , 它的 writeObject(Object obj)方法可以对参数指定的 obj 对象进行序列化,把得到的字节序列写到一个目标输出流中;

java.io.ObjectInputStream:表示对象输入流 ,它的 readObject()方法源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回;

需要注意的是,被序列化的对象需要实现 java.io.Serializable 接口。Java 的序列化机制是通过判断类的 serialVersionUID 来验证版本一致性的。在进行反序列化时,JVM 会把传来的字节流中的 serialVersionUID 与本地相应实体类的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是 InvalidCastException。

另外一个需要注意的就是transient关键字,被transient修饰的属性不会被序列化,如果从重写writeobject和readobject则可以重新被序列化。在JDK中的案例就是ArryList中修饰Object[]的数组使用transient关节字,保证传输过程中不照成浪费,只传输有用的值。本质是是通过反射来实现调用writeobject和readobject。

什么是Socket通信

Socket 的原意是“插座”,在计算机通信领域,Socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 Socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。


img

RPC原理介绍

RPC是什么

所谓的RPC其实是为了不同主机的两个进程间通信而产生的,通常不同的主机之间的进程通信,程序编写需要考虑到网络通信的功能,这样程序的编写将会变得复杂。RPC就来解决这一问题的,一台主机上的进程对另外一台主机的进程发起请求时,内核会将请求转交给RPC client,RPC client经过报文的封装转交给目标主机的RPC server,RPC server就将报文进行解析,还原成正常的请求,转交给目标主机上的目标进程。在我们看来在就像是在同一台主机上的两个进程通信一样,完全没有意识到是在不同的主机上。因此RPC其实也可以看做是一种协议或者是编程框架,目的是为了简化分布式程序的编写。

RPC基本流程


img
  1. Rpc Client通过传入的IP、端口号、调用类以及方法的参数,通过动态代理找到具体的调用类的方法,将请求的类、方法序列化,传输到服务端;

  2. 当Rpc Service收到请求以后,将传入类和方法反序列化,通过反射找到对应的类的方法进行调用,最后将返回结果进行序列化,返回客户端;

  3. Rpc Client收到返回值以后,进行反序列化,最后将结果展示;

手撸RPC

从RPC的基本流程可以看到,对于RPC性能来说可以提升的主要两个地方分别是序列化工具以及通信框架,在我们整个手撸系列里面会一步一步将其中的组件提升为高性能的组件,从阻塞IO到NIO,从JDK原始序列化框架到现在五花把门序列化框架,从手动的创建对象到Spring自动化创建对象,注册中心引入等等,整个过程还会伴随知识介绍,让我们一起携手共进。

迈出第一步

第一步我们只做到支持一个类的远程调用,采用JDK携带的序列化和反序列的工具以及阻塞连接的方式。


img

整体项目结构分为三部分,rpc-api作为Api提供,rpc-common主要是提供公共封装供client和service调用,rpc-v1包括rpc-v1-client主要是客户端调用封装,rpc-v1-service作为Api实现以及暴露对应方法,以后每次做的更改我都会新增一个版本,这样会方便新手进行学习。

Service端


img

服务端的实现采用ServerSocket监听某个端口,循环接收连接请求,如果发来了请求就创建一个线程,在新线程中处理调用,核心类就是RpcProxyService和ProcessorHandler,实现如下:

RpcProxyService
@Slf4j
public class RpcProxyService {

    private ExecutorService threadPool;

    public RpcProxyService() {
        int corePoolSize = 5;
        int maximumPoolSize = 200;
        long keepAliveTime = 60;
        BlockingQueue<Runnable> workingQueue = new ArrayBlockingQueue<>(100);
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("socket-pool-").build();
        threadPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workingQueue, threadFactory);
    }
    
    /**
     * 暴露方法,在注册完成以后服务后立刻开始监听
     *
     * @param service
     * @param port
     */
    public void register(Object service, int port) {

        try (ServerSocket serverSocket = new ServerSocket(port);) {
            Socket socket;
            while ((socket = serverSocket.accept()) != null) {
                log.info("客户端连接IP为:" + socket.getInetAddress());
                threadPool.execute(new ProcessorHandler(socket, service));
            }
        } catch (IOException e) {
            log.error("连接异常", e);
        }
    }
}
ProcessorHandler
@Slf4j
public class ProcessorHandler implements Runnable {

    private Socket socket;

    private Object service;

    public ProcessorHandler(Socket socket, Object service) {
        this.service = service;
        this.socket = socket;
    }

    @Override
    public void run() {

        try (ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream())) {
            ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
            //从输入流读取参数
            RpcRequest rpcRequest = (RpcRequest) inputStream.readObject();
            //通过反射获取到方法
            Method method = service.getClass().getMethod(rpcRequest.getMethodName(), rpcRequest.getParamTypes());
            //执行方法
            Object result = method.invoke(service, rpcRequest.getParameters());
            outputStream.writeObject(RpcResponse.ok(result));
            outputStream.flush();
        } catch (IOException | ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException exception) {
            //此处可再次进行包装将异常情况分类
            log.error("调用时发生错误", exception);
        }

    }
}

Client端


img

Client端通过RpcClientProxy动态代理(采用JDK动态代理)生成代理对象,然后通过执行RemoteInvocationHandler的invoke来确定调用具体的类和方法,也就是构建RpcRequest对象,最后通过RpcClient发起远程调用。

RpcClientProxy
public class RpcClientProxy {

    public <T> T getProxy(Class<T> interfaceClass, String host, int port) {
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),
                new Class<?>[]{interfaceClass},
                new RemoteInvocationHandler(host, port));
    }
}
RemoteInvocationHandler
public class RemoteInvocationHandler implements InvocationHandler {

    private String host;

    private int port;

    public RemoteInvocationHandler(String host, int port) {
        this.host = host;
        this.port = port;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //构造请求参数
        RpcRequest rpcRequest = RpcRequest.builder()
                .interfaceName(method.getDeclaringClass().getName())
                .methodName(method.getName())
                .parameters(args)
                .paramTypes(method.getParameterTypes())
                .build();
        //发送请求
        RpcClient rpcClient = new RpcClient();
        return ((RpcResponse) rpcClient.send(rpcRequest, host, port)).getData();
    }
}
RpcClient
@Slf4j
public class RpcClient {

    public Object send(RpcRequest rpcRequest, String host, int port) {
        try (Socket socket = new Socket(host, port)) {
            ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
            //序列化
            outputStream.writeObject(rpcRequest);
            outputStream.flush();
            return inputStream.readObject();
        } catch (IOException | ClassNotFoundException e) {
            log.error("调用时发生异常", e);
            return null;
        }
    }
}

Common端

Common端目前做入参和出参封装,代码如下:

RpcRequest
@Data
@Builder
public class RpcRequest implements Serializable {

    /**
     * 接口名称
     */
    private String interfaceName;

    /**
     * 方法名称
     */
    private String methodName;

    /**
     * 参数
     */
    private Object[] parameters;

    /**
     * 参数类型
     */
    private Class<?>[] paramTypes;

}
RpcResponse
@Data
public class RpcResponse<T> implements Serializable {

    /**
     * 状态码
     */
    private Integer code;

    /**
     * 提醒信息
     */
    private String message;

    /**
     * 返回信息
     */
    private T data;

    public static <T> RpcResponse<T> ok(T data) {
        RpcResponse<T> rpcResponse = new RpcResponse<>();
        rpcResponse.setCode(ResponseCode.SUCCESS.getCode());
        rpcResponse.setData(data);
        rpcResponse.setMessage(rpcResponse.getMessage());
        return rpcResponse;
    }

    public static <T> RpcResponse<T> error(int code, String message) {
        RpcResponse<T> rpcResponse = new RpcResponse<>();
        rpcResponse.setCode(code);
        rpcResponse.setMessage(message);
        return rpcResponse;
    }

}

整体代码我已经上传github,对于初学者一定要联调一下,理解清楚整体的RPC流程。

欢迎大家点点关注,点点赞!

手写RPC-简陋版的更多相关文章

  1. 手写RPC框架指北另送贴心注释代码一套

    Angular8正式发布了,Java13再过几个月也要发布了,技术迭代这么快,框架的复杂度越来越大,但是原理是基本不变的.所以沉下心看清代码本质很重要,这次给大家带来的是手写RPC框架. 完整代码以及 ...

  2. 看了这篇你就会手写RPC框架了

    一.学习本文你能学到什么? RPC的概念及运作流程 RPC协议及RPC框架的概念 Netty的基本使用 Java序列化及反序列化技术 Zookeeper的基本使用(注册中心) 自定义注解实现特殊业务逻 ...

  3. java 从零开始手写 RPC (03) 如何实现客户端调用服务端?

    说明 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 写完了客户端和服务端,那么如何实现客户端和服务端的 ...

  4. java 从零开始手写 RPC (04) -序列化

    序列化 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 java 从零开始手写 RPC (03) 如何实 ...

  5. java 从零开始手写 RPC (05) reflect 反射实现通用调用之服务端

    通用调用 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 java 从零开始手写 RPC (03) 如何 ...

  6. java 从零开始手写 RPC (07)-timeout 超时处理

    <过时不候> 最漫长的莫过于等待 我们不可能永远等一个人 就像请求 永远等待响应 超时处理 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RP ...

  7. 手写RPC框架(六)整合Netty

    手写RPC框架(六)整合Netty Netty简介: Netty是一个基于NIO的,提供异步,事件驱动的网络应用工具,具有高性能高可靠性等特点. 使用传统的Socket来进行网络通信,服务端每一个连接 ...

  8. 识别手写数字增强版100% - pytorch从入门到入道(一)

    手写数字识别,神经网络领域的“hello world”例子,通过pytorch一步步构建,通过训练与调整,达到“100%”准确率 1.快速开始 1.1 定义神经网络类,继承torch.nn.Modul ...

  9. 来,我们手写一个简易版的mock.js吧(模拟fetch && Ajax请求)

    预期的mock的使用方式 首先我们从使用的角度出发,思考编码过程 M1. 通过配置文件配置url和response M2. 自动检测环境为开发环境时启动Mock.js M3. mock代码能直接覆盖g ...

随机推荐

  1. apply 和 call 的区别

    相同点: 都能够改变方法的执行上下文(执行环境),将一个对象的方法交给另一个对象来执行,并且是立即执行 不同点: call方法从第二个参数开始可以接收任意个参数,每个参数会映射到相应位置的func的参 ...

  2. python实现skywalking的trace模块过滤和报警

    skywalking本身的报警功能,用起来视乎不是特别好用,目前想实现对skywalking的trace中的错误接口进行过滤并报警通知管理员和开发.所以自己就用python对skywalking做了二 ...

  3. Jmeter——SMTP Sampler发送邮件

    在平时测试过程中,也会出一些测试报告,那jmeter在不依托其他工具的情况下,可不可以发送邮件呢,自然是可以的. 我们直接使用SMTP Sampler即可. SMTP Sampler参数 我们来添加个 ...

  4. 转:KVC 与 KVO 理解

    KVC 与 KVO 理解 On 2012 年 6 月 7 日, in iPhone, by donly KVC 与 KVO 是 Objective C 的关键概念,个人认为必须理解的东西,下面是实例讲 ...

  5. Samba 源码解析之SMBclient命令流

    smbclient提供了类似FTP式的共享文件操作功能, 本篇从源码角度讲解smbclient的实现,smbclient命令的具体使用可通过help命令和互联网查到大量资料. 以下从源码角度分析一个s ...

  6. Spring 容器的启动过程 流程图 自己看源码的梳理 如有错错误 请指正

  7. 解决iwrite无法粘贴问题

    使用iwrite写作的时候,会遇到系统禁止粘贴的障碍 按F12键,再按F1键,在Disable JavaScrip前面的方框里打上勾就可以愉快的粘贴了

  8. LuoguB2103 图像相似度 题解

    Content 给定两个 \(m\times n\) 的矩阵 \(A,B\),求 \(A,B\) 两个矩阵的相似度,精确到小数点后 \(2\) 位. 定义两个矩阵的相似度为两个矩阵对应相同元素个数占矩 ...

  9. python 字符编码讲解

    ANSI不是一种具体的编码格式 ANSI在中文Windows操作系统代码指的是GBK编码 ANSI在中文Mac操作系统代码指的是UTF-8编码 ANSI在其他国家的操作系统中有其他的编码格式 #ASC ...

  10. Java 常用类库一,main方法传参String[] args;获取输入Scanner ;hasNext();hasNextInt()

    1. main方法传参 package com.zmd.common_class_libraries; /** 给mian方法传参测试 */ public class MainArgsTest { p ...