RPC模式的介紹以及簡單的實現

你丫的才程式設計師發表於2019-04-09

RPC

RPC(Remote Procedure Call,遠端過程呼叫)一般用來實現部署在不同機器上的系統之間的方法呼叫,使得程式能夠像訪問本地資源一樣,通過網路傳輸去訪問遠端系統資源。(此段百度的,不管來自哪兒,弄明白就是自己的)

RPC框架實現的架構原理:

下面來展示下本人拙劣的畫圖技術

rpc

Client:客戶端,服務消費者,負責發起RPC呼叫,通過遠端代理物件呼叫遠端服務。 Serialization/Deserilization:負責對RPC呼叫通過網路傳輸的內容進行序列化與反序列化。 Stub Proxy:代理物件,遮蔽RPC呼叫過程中複雜的網路處理邏輯,使得RPC呼叫透明化,能夠保持與本地呼叫一樣的程式碼風格。 TransPort:作為RPC框架底層的通訊傳輸模組,一般通過Socket在客戶端與服務端之間傳遞請求與應答資訊。

我所理解的原理過程(不對的地方求指教):

  • 服務消費方(client)呼叫以本地呼叫方式呼叫服務;
  • stub Proxy接收到呼叫後負責將方法、引數等組裝成可以序列化的Invocation物件;
  • stub Proxy找到服務地址,建立socket連線並將訊息傳送到服務端;
  • server端將接收到的資料反序列化,交給stub Proxy處理;
  • server端將stub收集到的呼叫資訊分發給具體的方法去執行,這裡並不是真正的client去執行該方法,而是在server端通過反射生成的client動態物件去執行;
  • RPC介面執行完畢,返回執行結果,思路類似。

RPC的簡易實現

技術方案

採用的是Socket通訊、動態代理與反射與Java原生的序列化,由於能力有限,只會基於BIO來實現,以後會嘗試用zookeeper、netty、redis等來實現!

具體實現

1、服務提供者提供的服務實現

package com.ooliuyue.simplRpc.test;

/**
 * @Auther: ly
 * @Date: 2019/3/14 11:45
 */

public class HelloServiceImpl implements HelloService {
    @Override
    public String hello(String name) {
        return "hello " + name;
    }
}

複製程式碼

2、客戶端獲取遠端服務,服務端暴露自己的服務,Socket來完成通訊工作(註釋比較詳細,一目瞭然)

package com.ooliuyue.simplRpc.rpc;

import com.ooliuyue.simplRpc.utils.StringUtils;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @Auther: ly
 * @Date: 2019/3/13 17:53
 */

public class SimpleRpc {
    /**
     *將指定服務暴露出來,供客戶端遠端呼叫
     * @param service 需要暴露的服務
     * @param port  暴露的埠號
     * @throws Exception
     */
    public static void export(final Object service,int port) throws Exception{
        if (service == null)
            throw new IllegalArgumentException("service not null");
        if (port < 0 || port > 65535)
            throw new IllegalArgumentException("port in [0,65535]");
        System.out.println("Export Service:" + service.getClass().getName() + " port:" + port );
        /**
         *  Socket程式設計步驟
         *  伺服器端建立ServerSocket物件,呼叫accept方法返回Socket物件
         *  客戶端建立Socket物件,通過埠連線到伺服器
         *  客戶端、伺服器端都使用Socket中的getInputStream方法和getOutputStream方法獲得輸入流和輸出流,
         *  進一步進行資料讀寫操作
         */
        /**
         * socket在伺服器端上的使用
         * 1.getInputStream方法得到的是一個輸入流,服務端的Socket物件上的getInputStream方法得到的輸入流其實就是從客戶端傳送給伺服器端的資料流。
         * 2.getOutputStream方法得到的是一個輸出流,服務端的Socket物件上的getOutputStream方法得到的輸出流其實就是傳送給客戶端的資料。
         */
        ServerSocket serverSocket = new ServerSocket(port);
        while (true) {
            final Socket socket = serverSocket.accept();
            new Thread(() -> {
                ObjectInputStream input = null;
                ObjectOutputStream output = null;
                try {
                    try {
                        //將客戶端傳送給服務端的資料流反序列化成物件
                        input = new ObjectInputStream(socket.getInputStream());
                        String methodName = input.readUTF();
                        Class<?>[] paramTypes = (Class<?>[]) input.readObject();
                        Object[] arguments = (Object[]) input.readObject();

                        Method method = service.getClass().getMethod(methodName, paramTypes);
                        //反射呼叫服務實現,獲取執行結果
                        Object result = method.invoke(service, arguments);

                        //將執行結果反序列化,然後傳送給客戶端
                        output = new ObjectOutputStream(socket.getOutputStream());
                        output.writeObject(result);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (input != null)
                        try {
                            input.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    if (output != null)
                        try {
                            output.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                }
            }).start();
        }
    }

    /**
     * 獲取遠端服務
     * @param interfaceClass 服務介面class
     * @param host 遠端IP地址
     * @param port 遠端埠號
     * @param <T> 指定介面的例項
     * @return
     * @throws Exception
     */
    /**
     *
     * socket在客戶端上的使用
     * 1.getInputStream方法可以得到一個輸入流,客戶端的Socket物件上的getInputStream方法得到輸入流其實就是從伺服器端返回的資料。
     * 2.getOutputStream方法得到的是一個輸出流,客戶端的Socket物件上的getOutputStream方法得到的輸出流其實就是傳送給伺服器端的資料。
     */
    public static <T> T getRemoteService(final Class<T> interfaceClass,final String host,final int port)
            throws Exception {
        verifyGetRemoteService(interfaceClass,host,port);
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),new Class<?>[]{interfaceClass},(Object proxy, Method method, Object[] args) ->{
            Socket socket = new Socket(host,port);
            System.out.println("get remote service :" + interfaceClass.getName() + " from " + host + ":" + port);

            //將遠端服務呼叫所需的介面類、方法名、引數列表等編碼後傳送給服務提供者
            ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
            output.writeUTF(method.getName());
            output.writeObject(method.getParameterTypes());
            output.writeObject(args);

            //result就是從伺服器返回的資料
            ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
            Object result = input.readObject();
            return  result;
        });

    }

    private static <T> void verifyGetRemoteService(final Class<T> interfaceClass, final String host, final int port) {
        if (interfaceClass == null)
            throw new IllegalArgumentException("interfaceClass not null");
        if (!interfaceClass.isInterface())
            throw new IllegalArgumentException("interfaceClass not a interface");
        if (!StringUtils.isNotBlank(host))
            throw new IllegalArgumentException("host not blank");
        if (port < 0 || port > 65535)
            throw new IllegalArgumentException("port in [0,65535]");
    }

}

複製程式碼

3、測試 首先服務端暴露服務,執行main

package com.ooliuyue.simplRpc.test;

/**
 * @Auther: ly
 * @Date: 2019/3/14 11:43
 */

import com.ooliuyue.simplRpc.rpc.SimpleRpc;

/**
 * 服務提供者
 */
public class Server {
    public static void main(String[] args) throws Exception {
        HelloService helloService = new  HelloServiceImpl();
        SimpleRpc.export(helloService,1000);
    }

}
複製程式碼

執行結果: Export Service:com.ooliuyue.simplRpc.test.HelloServiceImpl port:1000

然後客戶端進行呼叫,執行main

package com.ooliuyue.simplRpc.test;

import com.ooliuyue.simplRpc.rpc.SimpleRpc;

/**
 * @Auther: ly
 * @Date: 2019/3/14 11:43
 */

public class Client {
    public static void main(String[] args) throws Exception {
        HelloService remoteService = SimpleRpc.getRemoteService(HelloService.class, "127.0.0.1", 1000);
        String str = remoteService.hello("王大錘");
        System.out.println(str);
    }

}
複製程式碼

執行結果 get remote service :com.ooliuyue.simplRpc.test.HelloService from 127.0.0.1:1000 hello 王大錘

相關文章