分布式:RPC原理與實例實踐

參考鏈接
RPC相關(guān)
https://blog.csdn.net/weixin_38327420/article/details/85064617
http://www.itdecent.cn/p/32ca4fd5a7e2
http://www.itdecent.cn/p/5b90a4e70783
https://www.cnblogs.com/baizhanshi/p/6053213.html
java序列化
https://blog.csdn.net/so_geili/article/details/78931742

目錄

一、RPC的概念
二、RPC通信過程
三、RPC實例

一、RPC的概念

RPC:remote procedure call Protocol 遠程過程調(diào)用
RPC 是一種通過網(wǎng)絡(luò)從遠程計算機上請求服務(wù)但不需要關(guān)心底層的網(wǎng)絡(luò)通信細節(jié)的通信協(xié)議。簡單來說,RPC 是一種通信協(xié)議。

RPC允許像調(diào)用本地服務(wù)一樣調(diào)用遠程服務(wù),讓調(diào)用者對網(wǎng)絡(luò)通信細節(jié)透明。

RPC 的架構(gòu)

通常為 C/S 模型。一個典型的 RPC 結(jié)構(gòu)組成有如下幾個部分:

    1. 通信模塊

通信模塊就是用于在網(wǎng)絡(luò)上傳輸數(shù)據(jù)包的,它一般不會對數(shù)據(jù)包內(nèi)容作任何處理。 RPC 的通信有同步和異步兩種通信模式。

    1. Stub 程序

可以簡單理解成是一個代理。它上面記載了所有支持的遠程調(diào)用接口,服務(wù)端和客戶端各自持有一份完全相同的 Stub 程序。我們的 RPC 的過程,其本質(zhì)上就是去調(diào)用各自手里的這份 Stub 程序上的接口而已。

    1. 調(diào)度程序

調(diào)度器,運行在服務(wù)端。接收來自通信模塊的請求,并根據(jù)請求中的標識符來選擇對應(yīng)的 Stub 程序去執(zhí)行。

二、RPC通信過程

要讓網(wǎng)絡(luò)通信細節(jié)對使用者透明,我們需要對通信細節(jié)進行封裝,一個RPC調(diào)用的流程涉及的通信細節(jié)如下:

RPC通信過程

1.服務(wù)消費方(client)調(diào)用以本地調(diào)用方式調(diào)用服務(wù);
2.client stub接收到調(diào)用后負責(zé)將方法、參數(shù)等組裝成能夠進行網(wǎng)絡(luò)傳輸?shù)南Ⅲw;
3. client stub找到服務(wù)地址,并將消息發(fā)送到服務(wù)端;
4.server stub收到消息后進行解碼;
5.server stub根據(jù)解碼結(jié)果調(diào)用本地的服務(wù);
6.本地服務(wù)執(zhí)行并將結(jié)果返回給server stub;
7.server stub將返回結(jié)果打包成消息并發(fā)送至消費方;
8.client stub接收到消息,并進行解碼;
9.服務(wù)消費方得到最終結(jié)果。

三、RPC實例

本文利用的實例代碼,見 github
在本地模擬RPC運行原理,運行時先運行服務(wù)器(ProviderApp)端代碼,再運行客戶端(ComsumerApp)代碼。

利用RPC框架簡易實現(xiàn)1+2=3遠程調(diào)用的例子??蛻舳税l(fā)送兩個參數(shù),服務(wù)端返回兩個數(shù)字的相加結(jié)果。接下來對代碼的主要內(nèi)容進行說明。

主要分為客戶端、服務(wù)提供端,請求處理三個部分。

代碼結(jié)構(gòu)
1)服務(wù)端

服務(wù)端提供客戶端所期待的服務(wù),一般包括三個部分:服務(wù)接口,服務(wù)實現(xiàn)以及服務(wù)的注冊暴露三部分

  • 服務(wù)接口
    定義Calculate接口
public interface Calculator {
    int add(int a, int b);
}
  • 服務(wù)實現(xiàn)
    實現(xiàn)具體的加法計算,傳入兩個參數(shù),返回計算結(jié)果
public class CalculatorImpl implements Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}
  • 服務(wù)暴露,服務(wù)暴露功能實現(xiàn)
    只有把服務(wù)暴露出來,才能讓客戶端進行調(diào)用,這是RPC框架功能之一。

RPC服務(wù)端邏輯 :首先創(chuàng)建ServerSocket負責(zé)監(jiān)聽特定端口并接收客戶連接請求,然后使用Java原生的序列化/反序列化機制來解析得到請求,包括所調(diào)用方法的名稱、參數(shù)列表和實參,最后反射調(diào)用服務(wù)端對服務(wù)接口的具體實現(xiàn)并將得到的結(jié)果回傳至客戶端。至此,一次簡單PRC調(diào)用的服務(wù)端流程執(zhí)行完畢。

//服務(wù)暴露

public class ProviderApp {
    private static Logger log = LoggerFactory.getLogger(ProviderApp.class);

    private Calculator calculator = new CalculatorImpl();

    public static void main(String[] args) throws IOException {
        new ProviderApp().run();
    }

//服務(wù)端暴露服務(wù)具體實現(xiàn)
    private void run() throws IOException {
        ServerSocket listener = new ServerSocket(9090);
        try {
            while (true) {
                Socket socket = listener.accept();
                try {
                    // 將請求反序列化
                    ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
                    Object object = objectInputStream.readObject();

                    log.info("request is {}", object);

                    // 調(diào)用服務(wù)
                    int result = 0;
                    if (object instanceof CalculateRpcRequest) {
                        CalculateRpcRequest calculateRpcRequest = (CalculateRpcRequest) object;
                        if ("add".equals(calculateRpcRequest.getMethod())) {
                            result = calculator.add(calculateRpcRequest.getA(), calculateRpcRequest.getB());
                        } else {
                            throw new UnsupportedOperationException();
                        }
                    }

                    // 返回結(jié)果
                    ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
                    objectOutputStream.writeObject(new Integer(result));
                } catch (Exception e) {
                    log.error("fail", e);
                } finally {
                    socket.close();
                }
            }
        } finally {
            listener.close();
        }
    }

}
2)客戶端

客戶端調(diào)用服務(wù)端所提供的服務(wù),包括服務(wù)接口、服務(wù)引用兩個部分。

  • 服務(wù)接口:與服務(wù)端共享同一個服務(wù)接口
public interface Calculator {
    int add(int a, int b);
}
  • 服務(wù)引用
    消費端通過RPC框架進行遠程調(diào)用
public class ComsumerApp {
    private static Logger log = LoggerFactory.getLogger(ComsumerApp.class);

    public static void main(String[] args) {
        Calculator calculator = new CalculatorRemoteImpl();
        int result = calculator.add(1, 2);
        log.info("result is {}", result);
    }
}
  • 服務(wù)引用實現(xiàn)

通過CalculatorRemoteImpl,封裝客戶端引用RPC的邏輯。

RPC客戶端邏輯:首先創(chuàng)建Socket客戶端并與服務(wù)端建立鏈接,然后使用Java原生的序列化/反序列化機制將調(diào)用請求發(fā)送給客戶端,包括所調(diào)用方法的名稱、參數(shù)列表將服務(wù)端的響應(yīng)返回給用戶即可。至此,一次簡單PRC調(diào)用的客戶端流程執(zhí)行完畢。

分布式應(yīng)用下,一個服務(wù)可能有多個實例,比如Service B,可能有ip地址為198.168.1.11和198.168.1.13的兩個實例,lookupProviders其實就是在尋找要調(diào)用的服務(wù)的實例列表。在分布式應(yīng)用下,通常會有一個服務(wù)注冊中心,來提供查詢實例列表的功能。查到實例列表之后利用chooseTarget選擇具體調(diào)用哪個實例,內(nèi)部是一個負載均衡策略。

代碼僅實現(xiàn)了一個非常簡單的RPC框架,不考慮查找實例和動態(tài)代理問題,這里將端口固定為9090,ip地址為本地地址127.0.0.1

public class CalculatorRemoteImpl implements Calculator {
    public static final int PORT = 9090;
    private static Logger log = LoggerFactory.getLogger(CalculatorRemoteImpl.class);

    public int add(int a, int b) {
        List<String> addressList = lookupProviders("Calculator.add");
        String address = chooseTarget(addressList);
        try {
            Socket socket = new Socket(address, PORT);

            // 將請求序列化
            CalculateRpcRequest calculateRpcRequest = generateRequest(a, b);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());

            // 將請求發(fā)給服務(wù)提供方
            objectOutputStream.writeObject(calculateRpcRequest);

            // 將響應(yīng)體反序列化
            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
            Object response = objectInputStream.readObject();

            log.info("response is {}", response);

            if (response instanceof Integer) {
                return (Integer) response;
            } else {
                throw new InternalError();
            }

        } catch (Exception e) {
            log.error("fail", e);
            throw new InternalError();
        }
    }

    private CalculateRpcRequest generateRequest(int a, int b) {
        CalculateRpcRequest calculateRpcRequest = new CalculateRpcRequest();
        calculateRpcRequest.setA(a);
        calculateRpcRequest.setB(b);
        calculateRpcRequest.setMethod("add");
        return calculateRpcRequest;
    }

    private String chooseTarget(List<String> providers) {
        if (null == providers || providers.size() == 0) {
            throw new IllegalArgumentException();
        }
        return providers.get(0);
    }

    public static List<String> lookupProviders(String name) {
        List<String> strings = new ArrayList();
        strings.add("127.0.0.1");
        return strings;
    }
}
request

CalculateRpcRequest類里是序列化定義相關(guān)內(nèi)容,實現(xiàn)Serializable接口

public class CalculateRpcRequest implements Serializable {

    private static final long serialVersionUID = 7503710091945320739L;

    private String method;
    private int a;
    private int b;

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }
      
    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }

    public int getB() {
        return b;
    }

    public void setB(int b) {
        this.b = b;
    }

    @Override
    public String toString() {
        return "CalculateRpcRequest{" +
                "method='" + method + '\'' +
                ", a=" + a +
                ", b=" + b +
                '}';
    }
}
運行結(jié)果

如下,最終得到1+2=3的結(jié)果。

1+2=3

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容