轻松理解RPC框架原理——用Java徒手撕一个RPC框架

随着微服务的兴起,PRC成为了微服务中不可缺少的一部分,作为开发人员,我们不仅仅需要掌握RPC框架的使用,更需要去理解RPC框架的底层实现原理。请点击这里,代码已经上传到GitHub,欢迎一起学习探讨。

一、RPC框架是什么

RPC即Remote Procedure Call(远程过程调用),在之前我们开发的单体项目中,项目中的一个方法可能依赖于项目中的另外一个方法,但都在一台服务器中,比较容易理解,实现起来也比较简单。但这种思想开发的项目,非常不利于后期项目的维护和扩展,也常常受到单台服务器性能的影响。为此,我们需要学习PRC框架,接受一种新的开发模式,它需要我们将之前的单体引用进行分解,这些分解的应用可能会单独的部署到某一台独立的服务器中,此时我们就需要利用RPC框架将这些分布在不同服务器上的微服务联系起来,组成一个整体。对于访问应用的人来说,他并感觉不到背后的逻辑,和访问之前的单体应用没有什么区别。

降了一大推,其实就是分布式的知识,而分布式的实现就依赖于PRC框架

二、RPC框架的原理

在实现RPC框架之前,需要先了解以下RPC框架的原理

1、client端

对于client端来说,其需要调用远程的某一个方法,那么它就需要获取远程相应方法对应的一个实例,但是这个类又不在本地,该怎么实现呢?很容易就想到了动态代理的思想,可以想获取到远程对应方法的一个实例,通过这个实例,我们就可远程调用这个对应的方法了。那么问题又来了,就算获得到了一个代理的实例,还是和远程的方法产生不了联系啊。哈哈~ 两台计算机产生联系,自然就是网络通信啊。我们通过代理类中的invoke()方法,通过网络通信,来将要调用的方法,方法的参数传递过去。最后在通过网络通信,将远程server端返回的结果取回,就完成了RPC的过程。

2、server端

server端的实现就比较简单了,需要注册对外访问的接口,然后监听某个端口,等待连接之后,解析客户端传来的参数,然后调用本地的对应的方法,将产生的结果返回给客户端。

3、协议

这里的协议其实就是一种约定,本意上和我们常见的http协议没有什么区别,规定在网络中如何传递参数,方便server端对client传过来的数据进行解析。

三、RPC框架的实现

1、协议代码的编写

用于规定传递的参数格式,这里需要注意的是,由于这个类需要在网络中传输,那么就必须得实现serializable接口。

/**
 * RPC请求协议类,需要实现序列化接口
 * @author Time
 * @created 2019/12/19
 */
public class RPCProtocol implements Serializable {
    // 接口的全类名
    String interfaceClassName;
    // 方法名称
    String methodName;
    // 参数类型数组
    Class[] parameterTypes;
    // 参数对象数组
    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.客户端代码

客户端实现的思想上面已经讲过了,主要就是创建一个动态代理类,然后通过网络通信传递参数和获取返回结果。

/**
 * RPC框架核心实现类
 * @author Time
 * @created 2019/12/19
 */
public class RPCClient {
    /**
     * 通过动态代理获取到远程接口的具体实例,通过执行这个实例返回远程调用的结果
     * @param 
     * @return
     */
    public static  T getRemoteProxy(Class interfaceClass, InetSocketAddress inetAddress){
        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(inetAddress);
                    // 获取输入输出流
                    try(
                            ObjectOutputStream serialize = new ObjectOutputStream(socket.getOutputStream());
                            ObjectInputStream deSerialize = new ObjectInputStream(socket.getInputStream());
                            ){
                        // 创建一个rpc框架的请求协议对象
                        RPCProtocol rpcProtocol = new RPCProtocol();
                        // 填充属性
                        rpcProtocol.setInterfaceClassName(interfaceClass.getName());
                        rpcProtocol.setMethodName(method.getName());
                        rpcProtocol.setParameterTypes(method.getParameterTypes());
                        rpcProtocol.setParameterValues(args);
                        // 协议对象序列化,进行网络传输
                        serialize.writeObject(rpcProtocol);

                        // 服务端生成的结果反序列化
                        Object result = deSerialize.readObject();
                        // 结果返回
                        return result;
                    }

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

3、server端代码

这里通过创建一个线程池,可以处理多个客户端的请求。

/**
 1. RPC框架核心服务类
 2. 步骤:
 3. 1 暴露调用接口
 4. 2 启动服务
 5. @author Time
 6. @created 2019/12/19
 */
public class RPCServer {
    // 定义存储暴露的接口
    HashMap hashMap = new HashMap<>();
    // 定义一个线程池
    ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(8, 20, 200 , TimeUnit.MICROSECONDS,
            new ArrayBlockingQueue(10)
            );

    //  暴露服务的接口
    public void publicServiceAPI(Class clazz, Object instance){
        this.hashMap.put(clazz.getName(), instance);
    }

    // 发布服务的方法
    public void start(int port){
        // 创建网络服务端
        try {
            ServerSocket serverSocket = new ServerSocket();
            //绑定端口哦
            serverSocket.bind(new InetSocketAddress(port));
            System.out.println("===================RPC Server服务端启动成功===================");

            // 创建客户端处理请求的线程
            while (true){
                poolExecutor.execute(new ServerTask(serverSocket.accept()));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    private class ServerTask implements Runnable{
        private Socket socket;
        public ServerTask(Socket socket){
            this.socket = socket;

        }
        @Override
        public void run() {
            try(
                    ObjectInputStream deserialize = new ObjectInputStream(socket.getInputStream());
                    ObjectOutputStream serialize = new ObjectOutputStream(socket.getOutputStream())
            ){
                // 反序列获取客户端的协议
                RPCProtocol rpcProtocol = (RPCProtocol) deserialize.readObject();
                // 通过客户端传来的值获取对应的接口
                System.out.println(rpcProtocol.getInterfaceClassName());
                Object instance = hashMap.get(rpcProtocol.getInterfaceClassName());
                // 利用反射通过实例以及参数获取调用的具体方法
                Method method = instance.getClass().getDeclaredMethod(rpcProtocol.getMethodName(),
                        rpcProtocol.getParameterTypes());
                //  执行方法获取对应的结果
               Object result =  method.invoke(instance,rpcProtocol.getParameterValues());
               // 将执行的结果序列化,传回给客户端
                serialize.writeObject(result);

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

        }
    }
}
4、测试
  1. 服务端实例
public class App 
{
    public static void main( String[] args )
    {
        // 获取服务端
        RPCServer server = new RPCServer();
        // 暴露接口
        server.publicServiceAPI(UserService.class, new UserServiceImpl());
        // 发布服务
        server.start(8888);
    }
}

  1. 客户端实例
public class App 
{
    public static void main( String[] args )
    {
        UserService userService = RPCClient.getRemoteProxy(UserService.class,new InetSocketAddress("127.0.0.1",8888));
        String result = userService.addUserName("王五");
        System.out.println(result);
    }
}

总结:上面只是简单的实现,说明了其基本原理,代码已经上传到github。但目前流行的RPC框架则需要考虑更多的东西,如序列话、负载均衡、熔断器、动态监控等内容,有兴趣的话可以了解以下目前流行的RPC框架,如阿里的Double、新浪的Motan等。

你可能感兴趣的:(分布式,JAVA)