一个最简单的RPC实现及其原理

1 篇文章 0 订阅
1 篇文章 0 订阅
需要具备的知识点

       1. Java的网络通信Socket和IO,忘记的赶紧去百度一个基于Socket的聊天的Demo,就差不多了。为什么要学习Socket和IO呢?因为很大部分RPC框架底层就是基于Socke进行通信的,所以他是必不可少的知识点;
       2. Java的JDK动态代理知识;
       3. Java的序列化知识,简单理解,就是把java对象进行序列化用于网络传输,使得机器和机器之间方便进行数据通信的。
       4. Java的反射基础知识;

RPC介绍
什么是RPC?

       RPC(Remote Procedure Call Protocol)—— 远程过程调用协议,它是一种通过网络请求从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。

为什么需要RPC?

       因为近年来,随着分布式和微服务技术的发展,把单个服务程序按照功能拆分成多个模块,并把每个模块部署到多台机器上,多个模块之间相互配合提供服务。而多个机器之间相互配合需要进行通信,而如果直接写原生的Socket代码进行通信会造成代码冗余、资源浪费等。因此就产生了RPC框架,而RPC框架就是为机器之间进行通信服务的,让开发者不必关心底层实现,只需要按照指定的即可。
       作为程序员要做的不仅是学会如何使用,而且还要理解它的底层原理,这样才成为一位合格的开发者。

有哪些RPC的产物

       Dubbo、Thrift、gRpc等。

我们要做什么

       本章是,自定义一个简单的RPC,主要的功能是实现远程接口服务调用,至于一些其他的功能不会实现。本章主要讲的是调用的原理,知道了原理就大致了解了大部分RPC框架的是如何通信工作的。只需要知道调用的原理即可,其他的细节的东西不会多说,有些兴趣的话可以留言或者自己查看。

RPC的简单流程模型

一个简单的RPC调用过程,大致流程:
最简单的理解是,客户端封装一个服务端对象(包含类名,方法名,参数)通过socket传递给服务端,服务端拿到这个对象进行反射执行该类的方法,然后得到结果,在通过socket返回给客服端这个过程,就是一个rpc通信的基本过程。其他框架的也都是在此基础上进行封装,提供更加丰富的功能。下面是代码的实现过程:
在这里插入图片描述

一、服务端A实现

首先建立一个服务端A,创建一个maven工程simple-rpc-server,然后在simple-rpc-server下创建两个子模块simple-rpc-api、simple-rpc-provider。
simple-rpc-provider是为服务端私有的,不会发布出去,他的作用是simple-rpc-api接口的具体实现。
在这里插入图片描述

  • A、simple-rpc-api
           simple-rpc-api是服务接口的提供,目的用于暴露接口,给客户端使用,使用方式是把simple-rpc-api打包发布到本地仓库或者私服上,客户端引用该坐标进行接口调用。该模块下pojo包就三个类,这三个类可以单独提出来到一个maven工程中,然后引用坐标。但是一切从简,就写到带模块中。
    在这里插入图片描述
    1、User这个类就是一个简单的实体类,几个属性和get/set方法,并实现序列化接口
public class User implements Serializable {
    private Integer uid;//uid
    private String username;//用户名
    private String password;//密码
    private String name;//名称
    private Integer age;//年龄
    public Integer getUid() {
        return uid;
    }
    public void setUid(Integer uid) {
        this.uid = uid;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
}

2、RpcRequest这个类是最主要的,也是核心,作用是客户端把要请求的api信息,包装成RpcRequest对象,通过socket发送给服务端,然后服务端对对象进行解析,通过反射调用服务端的实现方法,在把执行的结果封装通过socket返回给客户端,这就完成了Rpc的远程调用。也是这个类的作用所在。并实现序列化接口

public class RpcRequest implements Serializable {
    //类的全路径
    private String className;
    //方法的名称
    private String methodName;
    //方法的参数
    private Object[] params;
    public String getClassName() {
        return className;
    }
    public void setClassName(String className) {
        this.className = className;
    }
    public String getMethodName() {
        return methodName;
    }
    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }
    public Object[] getParams() {
        return params;
    }
    public void setParams(Object[] params) {
        this.params = params;
    }
}

3、RpcResponse这个类,为服务端对象RcRequest解析执行,得到的结果包装成RpcResponse对象返回给客户端。

public class RpcResponse implements Serializable {
    private Integer code;
    private String message;
    private Object result;
    public RpcResponse(Integer code, String message, Object result) {
        this.code = code;
        this.message = message;
        this.result = result;
    }
    public static RpcResponse success(Object result){
        return new RpcResponse(200, "结果正确", result);
    }
    public static RpcResponse failed(String message){
        return new RpcResponse(500, message, null);
    }
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
    public Object getResult() {
        return result;
    }
    public void setResult(Object result) {
        this.result = result;
    }
}

4、UserService这个接口就是simple-rpc-server这个服务需要提供的功能,有保存user类和根据uid获取到user类

public interface UserService {
    public void saveUser(User user);
    public User getUserByUid(Integer uid);
}
  • B、simple-rpc-provider

       simple-rpc-provider是simple-rpc-api的实现,主要用于发布服务,用于接收服务端提交过来的RcRequest参数,然后通过反射调用实现方法进行执行,得到返回结果,封装并返回。该模块要引用simple-rpc-api的引用,对他的接口进行实现。该过程对应的是上图的的蓝色箭头。
在这里插入图片描述
1、UserServiceImpl为simple-rpc-api接口UserService的实现

public class UserServiceImpl implements UserService {
    //模拟数据库功能,进行存取
    private static Map<Integer, User> userMap = new HashMap();
    static {
        User user1 = new User();
        user1.setUid(10);
        user1.setName("zhangsan");
        user1.setAge(32);
        user1.setPassword("OOSDIAIDSI123213");
        user1.setUsername("41122400");
        userMap.put(user1.getUid(), user1);

        User user2 = new User();
        user2.setUid(12);
        user2.setName("lisi");
        user2.setAge(22);
        user2.setPassword("OSVASDASDASC112");
        user2.setUsername("41122400");
        userMap.put(user2.getUid(), user2);
    }
    @Override
    public void saveUser(User user) {
        //保存操作
        userMap.put(user.getUid(), user);

        System.out.println("数据库中的数据:");
        for (User dbUser : userMap.values()) {
            System.out.println(dbUser);
        }
    }
    @Override
    public User getUserByUid(Integer uid) {
        return userMap.get(uid);
    }
}

2、ProxtService为服务端的代理层(参照模型图),主要用于发布服务,并处理来自客户端的请求,并响应结果。底层是基于Socket实现的。

public class ProxyService {
    //创建一个线程池
    private static ExecutorService executorService = new ThreadPoolExecutor(5, 10, 3, TimeUnit.MINUTES, new ArrayBlockingQueue<>(10));
    public static void publish(int port, Object instance){
        System.out.println("RPC服务器 已启动, 端口为:" + port);
        try {
            ServerSocket serverSocket = new ServerSocket(port);
            while (true){
                //socket为客户端的一个引用,到该方法会连接阻塞
                Socket socket = serverSocket.accept();
                //使用线程池来处理请求,避免阻塞
                executorService.execute(new RpcHandler(socket, instance));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3、RpcHandler是用来做真正的业务处理的

public class RpcHandler implements Runnable {

    private Socket socket;
    private Object instance;//暴露哪一个实例

    public RpcHandler(Socket socket, Object instance) {
        this.socket = socket;
        this.instance = instance;
    }

    @Override
    public void run() {
        ObjectInputStream objectInputStream = null;
        ObjectOutputStream objectOutputStream = null;
        try {
            //获取到客户端的输入,其实就是RpcRequest对象
            objectInputStream = new ObjectInputStream(socket.getInputStream());
            RpcRequest rpcRequest = (RpcRequest) objectInputStream.readObject();
            //把rpcReuqest交给MethodHandler方法处理,通过反射调用执行,并得到返回结果
            RpcResponse rpcResponse = MethodHandler.process(instance, rpcRequest);
            //获取到输出流,把返回结果写出到客户端
            objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            objectOutputStream.writeObject(rpcResponse);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            if(objectInputStream != null){
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(objectOutputStream != null){
                try {
                    objectOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4、MethodHandler是用来进行对RpcRequest进行分析,通过反射调用对象的实现方法,本例中是UserServiceImpl类中的方法,并返回结果,对其进行包装RpcRequest。

public class MethodHandler {
    public static RpcResponse process(Object instance, RpcRequest rpcRequest){
        //获取到类的全路径
        String className = rpcRequest.getClassName();
        //获取参数的类信息,用于指定方法获取
        Object[] params = rpcRequest.getParams();
        Class[] paramsType = null;
        if(params != null || params.length != 0){
            paramsType = new Class[params.length];
            for (int i = 0; i < params.length; i++) {
                paramsType[i] = params[i].getClass();
            }
        }
        try {
            Class<?> clazz = Class.forName(className);
            Method method = clazz.getMethod(rpcRequest.getMethodName(), paramsType);
            Object result = method.invoke(instance, params);
            return RpcResponse.success(result);
        } catch (ClassNotFoundException e) {
            return RpcResponse.failed("类不存在");
        } catch (NoSuchMethodException e) {
            return RpcResponse.failed("方法不存在");
        } catch (IllegalAccessException e) {
            return RpcResponse.failed("访问异常");
        } catch (InvocationTargetException e) {
            return RpcResponse.failed("调用方法失败");
        }
    }
}

5、App类为我们的启动测试类,用于发布服务:

public class AppService {
    public static void main(String[] args) {
        //要发布服务端实例
        UserService userService = new UserServiceImpl();
        ProxyService.publish(8080, userService);
    }
}

到这来我们的服务端,以及底层的原理基本以及完成了,当启动的时候会在连接的时候一直阻塞,直到有客户端连接服务端,服务端才开始工作。
在这里插入图片描述

二、客户端A实现

可以在另一台电脑上或者同一个电脑上建立一个客户端A,创建一个maven工程simple-rpc-client,然后在simple-rpc-server下创建一个子模块simple-rpc-consumer。
在这里插入图片描述

  • A、simple-rpc-consumer

simple-rpc-consumer服务主要是对simple-rpc-api里面的接口进行代理,封装RpcRequest对象通过socket传递给服务端,让服务端解析对象并执行,在得到结果,返回给客户端。
并且记得引入simple-rpc-api在仓库中的引用位置。
在这里插入图片描述
1、ProxyService主要对接口进行代理

public class ProxyService {
    private int port;
    private String url;
    public ProxyService(String url, int port) {
        this.port = port;
        this.url = url;
    }
    //根据传递的接口类型信息,进行代理
    public Object proxy(Class<?> clazz){
        //得到代理的对象
        Object obj;
        obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{clazz}, new RpcHandler(url, port));
        return obj;
    }
}

2、RpcHandler为具体执行方法,当代用接口的getUserById方法时,会执行该类的invoke方法,封装对象传递给服务端执行,并返回结果。

public class RpcHandler implements InvocationHandler {
    private String url;
    private int port;

    public RpcHandler(String url, int port) {
        this.url = url;
        this.port = port;
    }

    //为UserService的代理对象,当带哦用UserService的任何方法时,都会调用invoke这个方法
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //只需要封装好RpcRequest对象,传给服务端即可
        RpcRequest rpcRequest = new RpcRequest();
        rpcRequest.setClassName(method.getDeclaringClass().getName());
        rpcRequest.setMethodName(method.getName());
        rpcRequest.setParams(args);
        //端口写死,可以自己做修改
        Socket socket = new Socket(url, port);
        //获取到输出流,把rpcRequest对象传到服务端,让服务端进行解析
        ObjectOutputStream os = new ObjectOutputStream(socket.getOutputStream());
        os.writeObject(rpcRequest);
        //获取到输入流,从服务器端获取到执行的结果,并对其进行解析
        ObjectInputStream is = new ObjectInputStream(socket.getInputStream());
        RpcResponse rpcResponse = (RpcResponse) is.readObject();
        if(rpcResponse.getCode() == 200){
            return rpcResponse.getResult();
        }
        return null;
    }
}

到这里客户端A就已经写完。

并添加一个测试类:

在这里插入图片描述

public class AppClient {
    public static void main(String[] args) {
        //创建一个代理对象,对接口进行代理
        ProxyService proxyService = new ProxyService("localhost", 8080);
        //获取代理对象,当调用方法的时候,其实是执行代理对象的invoke方法
        UserService userService = (UserService) proxyService.proxy(UserService.class);
        //获取id为10的用户
        User user = userService.getUserByUid(10);
        System.out.println(user);
        
        User user1 = new User();
        user1.setUid(1001);
        user1.setName("rpc Cient");
        //执行保存操作
        userService.saveUser(user1);
    }
}

先启动服务端,在启动客户端,会得到一下执行结果:
客服端:
在这里插入图片描述
服务端:
在这里插入图片描述
到这里就完成了rpc的远程调用。可以参照之前的图在此对比一边,只是一个简单的rpc调用过程,只需要知道他大致是如何实现的,一些其他功能可自己进行扩展。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值