一個最簡單的RPC實現及其原理

需要具備的知識點

       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調用過程,只需要知道他大致是如何實現的,一些其他功能可自己進行擴展。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章