实现自己的RPC框架(学习笔记)

1.基本概念

RPC是Remote Procedure Call的缩写,即远程过程调用。最基本的RPC模型如下图所示。
在下图中,服务提供者A Server、服务消费者B Server。服务消费者只需要通过接口,就可以远程调用服务提供者提供的对应的接口的实现,从而获取返回值,完成对应的调用过程。
在这里插入图片描述

2.具体实现

以下上代码实例,通过Socket的方式,来实现自己的RPC框架。项目结构如下图:
在这里插入图片描述
项目分为四个模块:rpc模块、公共接口模块、服务提供者模块、服务消费者模块。

2.1 rpc模块

rpc模块主要实现了RPC远程过程调用的功能。该模块中,分为三个部分,client端、server端、通信协议。如下图所示。
在这里插入图片描述

2.1.1通信协议

在RPC框架中,客户端的方法要通过接口调用服务端对应接口的实现类的方法,需要提供必要的信息。具体包括:接口名称、方法名称、参数类型列表、参数值列表。代码如下所示:

package com.supger.rpc.core.protocol;

import java.io.Serializable;

/**
 * RPC框架请求协议类,由于这个类需要在网络中传输,需要实现序列化接口。
 */
public class RequestProtocol implements Serializable {
    
    
    /**
     * 接口全名称
     */
    private String interfaceClassName;
    /**
     * 方法名称
     */
    private String methodName;
    /**
     * 参数类型列表
     */
    private Class<?>[] parameterTypes;
    /**
     * 参数值列表
     */
    private Object[] parameterValues;


    public String getInterfaceClassName() {
    
    
        return interfaceClassName;
    }

    public void setInterfaceClassName(String interfaceClassName) {
    
    
        this.interfaceClassName = interfaceClassName;
    }

    public String getMethodName() {
    
    
        return methodName;
    }

    public void setMethodName(String methodName) {
    
    
        this.methodName = methodName;
    }

    public Class<?>[] getParameterTypes() {
    
    
        return parameterTypes;
    }

    public void setParameterTypes(Class<?>[] parameterTypes) {
    
    
        this.parameterTypes = parameterTypes;
    }

    public Object[] getParameterValues() {
    
    
        return parameterValues;
    }

    public void setParameterValues(Object[] parameterValues) {
    
    
        this.parameterValues = parameterValues;
    }
}

2.1.2客户端

在RPC框架中,客户端通过动态代理,在动态代理中,把需要调用的服务端的接口的相关信息序列化,最终通过Socket的方式,发送给服务端,等待服务端完成调用,最终把结果写入Socket,动态代理内部,获取返回结果。返回给调用方。代码如下所示:

package com.supger.rpc.core.client;

import com.supger.rpc.core.protocol.RequestProtocol;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;

/**
 * RPC框架客户端核心实现类
 */
public class RpcClient {
    
    
    /**
     * 通过动态代理获取调用接口对应实例
     * @param interfaceClass
     * @param address
     * @param <T>
     * @return
     */
    public static <T> T getRemoteProxy(Class<T> interfaceClass, InetSocketAddress address){
    
    
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),
                new Class<?>[]{
    
    interfaceClass},
                new InvocationHandler() {
    
    
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
                        try(Socket socket = new Socket()){
    
    
                            // 通过网络连接服务端
                            socket.connect(address);
                            try(
                                    // 获取输出流
                                    ObjectOutputStream serializer = new ObjectOutputStream(socket.getOutputStream());
                                    // 获取输入流
                                    ObjectInputStream deSerializer = new ObjectInputStream(socket.getInputStream())
                                    ){
    
    
                                // 创建一个RPC框架中请求协议对象
                                RequestProtocol requestProtocol = new RequestProtocol();
                                // 填充属性
                                requestProtocol.setInterfaceClassName(interfaceClass.getName());
                                requestProtocol.setMethodName(method.getName());
                                requestProtocol.setParameterTypes(method.getParameterTypes());
                                requestProtocol.setParameterValues(args);
                                // 序列化协议对象(把数据放入到网络中)
                                serializer.writeObject(requestProtocol);
                                /**
                                 * -----------------------
                                 * 同步过程
                                 * -----------------------
                                 */
                                // 反序列化(从网络中获取服务端放入的数据)
                                Object result = deSerializer.readObject();
                                return  result;
                            }
                        }catch (Exception e){
    
    
                            e.printStackTrace();
                        }
                        return null;
                    }
                });
    }
}

2.1.3服务端

在RPC框架中,服务端需要启动,监听对应的端口。客户端来连接对应的端口。启动监听后,当有多个客户端请求来的时候,为了不因为前一个请求未处理完成而发生阻塞,需要引入多线程机制。当服务端监听到客户端连接的时候,用线程池来处理对应的任务。任务中保存着对应获取的Socket连接信息。在任务的内部,run方法中,实现对应的获取客户端的调用信息,通过反射的方式,调用对应的实现类。而在此之前,服务端需要暴露对应的服务列表。代码如下所示:

package com.supger.rpc.core.server;

import com.supger.rpc.core.protocol.RequestProtocol;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * RPC框架服务端的核心实现类
 * 核心实现步骤:
 * 1、暴露需要调用的服务接口
 * 2、启动服务端
 */
public class RpcServer {
    
    

    /**
     * 定义存储暴露的服务列表
     */
    Map<String,Object> serverMap = new ConcurrentHashMap<>(32);
    /**
     * 定义一个线程池
     */
    ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(8,20,200, TimeUnit.MICROSECONDS, new ArrayBlockingQueue<>(10));

    /**
     * 暴露服务的方法
     * @param interfaceClass
     * @param instance
     */
    public void publishServiceAPI(Class<?> interfaceClass,Object instance){
    
    
        this.serverMap.put(interfaceClass.getName(),instance);
    }

    /**
     * 定义发布服务的方法
     * @param port
     */
    public void start(int port){
    
    
        // 创建网络服务端
        try {
    
    
            ServerSocket serverSocket = new ServerSocket();
            // 绑定指定的端口
            serverSocket.bind(new InetSocketAddress(port));
            System.out.println("=============Supger RPC Server Starting ...... ================");
            while (true){
    
    
                poolExecutor.execute(new ServerTask(serverSocket.accept()));
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     * 创建客户端请求处理的线程类
     */
    private class ServerTask implements Runnable{
    
    

        private final Socket socket;

        private ServerTask(Socket socket) {
    
    
            this.socket = socket;
        }

        @Override
        public void run() {
    
    
            try(
                    ObjectInputStream deSerializer = new ObjectInputStream(socket.getInputStream());
                    ObjectOutputStream serializer = new ObjectOutputStream(socket.getOutputStream())
                    ){
    
    
                // 反序列化获取客户端传入的数据
                RequestProtocol requestProtocol = (RequestProtocol) deSerializer.readObject();
                // 获取接口全名称
                String interfaceClassName = requestProtocol.getInterfaceClassName();
                Object instance = serverMap.get(interfaceClassName);
                if (null == instance){
    
    
                    return;
                }
                // 创建一个方法对象(反射方式)
                Method method = instance.getClass()
                        .getDeclaredMethod(requestProtocol.getMethodName(), requestProtocol.getParameterTypes());
                // 调用方法
                Object result = method.invoke(instance, requestProtocol.getParameterValues());
                //  序列化调用结果
                serializer.writeObject(result);

            }catch (Exception e){
    
    
                e.printStackTrace();
            }
        }
    }
}

2.2 user-api

在RPC系统中,远程过程调用需要遵循一套统一的API模块和统一的java-bean。于是,把对应的api和javabean抽取出来,形成一个单独的模块(项目)。远程过程调用,就基于这样的一套标准接口。这里仅举一个简单的例子,目的在于帮助理解RPC的原理及实现。代码如下所示:

package service;

/**
 * 定义暴露给客户端的服务接口
 */
public interface UserService {
    
    
    String addUserName(String name);
}

2.3 user-provider

在RPC系统中,服务提供者,即服务的提供方。需要实现提供服务的对应的接口。即实现2.2 user-api中UserService接口。实现类代码如下所示。

package com.supger.service;

import service.UserService;

public class UserServiceImpl implements UserService {
    
    
    @Override
    public String addUserName(String name) {
    
    
        return "添加的姓名为:"+name;
    }
}

需要强调的是,该处实现的接口,需要引入公共的接口。
在实现接口之后,需要启动对应的服务,对应的代码如下所示。

package com.supger;

import com.supger.rpc.core.server.RpcServer;
import com.supger.service.UserServiceImpl;
import service.UserService;

public class App {
    
    
    public static void main(String[] args) {
    
    
        // 创建一个RPC的服务端
        RpcServer rpcServer = new RpcServer();
        // 发布暴露服务
        rpcServer.publishServiceAPI(UserService.class,new UserServiceImpl());
        // 启动服务
        rpcServer.start(12345);
    }
}

在上述代码中,先创建一个RPC的服务端。然后发布暴露对应的服务。最后启动服务。

2.4 user-consumer

在完成上述步骤之后,最后创建一个服务的消费者,在消费者中,通过调用RPC框架。完成对应的远程过程调用,就像调用本地方法一样简单,对应的代码如下所示。

package com.supger;

import com.supger.rpc.core.client.RpcClient;
import service.UserService;

import java.net.InetSocketAddress;

public class App {
    
    
    public static void main(String[] args) {
    
    
        // 在客户端调用RPC框架

        UserService userService = RpcClient.getRemoteProxy(UserService.class, new InetSocketAddress("127.0.0.1", 12345));

        String result = userService.addUserName("supger");

        System.out.println(result);
    }
}

至此,完成了实现自己的RPC框架的的流程。

3.运行测试

启动服务提供模块,如下图所示,表明服务已成功启动。
在这里插入图片描述
启动服务消费模块,如下图所示。
在这里插入图片描述
从运行结果可知,通过以上过程,实现了自己的RPC框架。

4. 结语

该过程涉及到Java中的一些基本的知识,具体包括:反射、序列化流、动态代理、多线程、线程池。后续会对相关概念做一个详尽的总结,目的一方面在于打好基本功,同时方便各位学习交流之用。
上述的实现过程,仅作为对RPC原理认识的一个初步方案。后续会学习RPC实现的相关框架Dubbo,等。届时,会进一步加深对RPC系统需要考虑的多方面的因素进行探讨。
关于RPC探讨的文章有很多,以下列出了一些参考文章。从不同的文章出发,会有不同的收获。从而加深对RPC进一步的理解。(本文为基于参考资源1的整理,特此注明参考来源)

5.参考资源

1.原来RPC框架原理这么学习,完全是一种享受!
2.初识RPC
3.RPC基本原理

猜你喜欢

转载自blog.csdn.net/fanjianglin/article/details/112213296