RPC入門理解

背景

最近幾天準(zhǔn)備學(xué)習(xí)thrift,百度百科解釋:

Thrift是一種接口描述語(yǔ)言和二進(jìn)制通訊協(xié)議,它被用來(lái)定義和創(chuàng)建跨語(yǔ)言的服務(wù)。它被當(dāng)作一個(gè)遠(yuǎn)程過(guò)程調(diào)用(RPC)框架來(lái)使用,是由Facebook為“大規(guī)??缯Z(yǔ)言服務(wù)開(kāi)發(fā)”而開(kāi)發(fā)的。

RPC又是啥?,忘光了,先從學(xué)習(xí)RPC開(kāi)始

RPC概念

RPC,遠(yuǎn)程過(guò)程調(diào)用是一種技術(shù)思想而非一種規(guī)范或協(xié)議,常見(jiàn)的RPC框架和技術(shù)有:

  • 應(yīng)用級(jí)的服務(wù)框架:阿里的 Dubbo/Dubbox、Google gRPC、Spring Boot/Spring Cloud。
  • 遠(yuǎn)程通信協(xié)議:RMI、Socket、SOAP(HTTP XML)、- REST(HTTP JSON)。
  • 通信框架:MINA 和 Netty。
    目前流行的開(kāi)源 RPC 框架還是比較多的,有阿里巴巴的 Dubbo、Facebook 的 Thrift、Google 的 gRPC、Twitter 的 Finagle 等。

其中,Thrift是 Facebook 的開(kāi)源 RPC 框架,主要是一個(gè)跨語(yǔ)言的服務(wù)開(kāi)發(fā)框架。
用戶只要在其之上進(jìn)行二次開(kāi)發(fā)就行,應(yīng)用對(duì)于底層的 RPC 通訊等都是透明的。不過(guò)這個(gè)對(duì)于用戶來(lái)說(shuō)需要學(xué)習(xí)特定領(lǐng)域語(yǔ)言這個(gè)特性,還是有一定成本的。

RPC實(shí)現(xiàn)

如果系統(tǒng)是單體應(yīng)用,因?yàn)檎ν粋€(gè)進(jìn)程,A想要調(diào)用B接口直接調(diào)用B()就可以了


image.png

如果采用分布式應(yīng)用,就不能這樣直接調(diào)用了


image.png

服務(wù)A和服務(wù)B并沒(méi)有在同一個(gè)進(jìn)程,必須采用網(wǎng)絡(luò)調(diào)用

REST接口調(diào)用

模仿B/S架構(gòu),B應(yīng)用暴露接口給A應(yīng)用,A、B之間通過(guò)HTTPS來(lái)進(jìn)行調(diào)用,但是A如果想使用HTTPS,在使用前就必須要編寫(xiě)HTTPS連接的代碼,會(huì)造成很多重復(fù)代碼,可以使用AOP切面編程,在業(yè)務(wù)邏輯開(kāi)始和結(jié)束后,連接和釋放連接。

RPC調(diào)用

如果采用HTTPS的方式,每次調(diào)用都要新建連接和釋放連接。我們可以使用Socket。都可以,RPC并沒(méi)有規(guī)定說(shuō)你要用何種協(xié)議進(jìn)行通訊。

image.png
  • Service A的應(yīng)用層代碼中,調(diào)用了Calculator的一個(gè)實(shí)現(xiàn)類的add方法,希望執(zhí)行一個(gè)加法運(yùn)算;
  • 這個(gè)Calculator實(shí)現(xiàn)類,內(nèi)部并不是直接實(shí)現(xiàn)計(jì)算器的加減乘除邏輯,而是通過(guò)遠(yuǎn)程調(diào)用Service B的RPC接口,來(lái)獲取運(yùn)算結(jié)果,因此稱之為Stub;
  • Stub怎么和Service B建立遠(yuǎn)程通訊呢?這時(shí)候就要用到遠(yuǎn)程通訊工具了,也就是圖中的Run-time Library,這個(gè)工具將幫你實(shí)現(xiàn)遠(yuǎn)程通訊的功能,比如Java的Socket,就是這樣一個(gè)庫(kù),當(dāng)然,你也可以用基于Http協(xié)議的HttpClient,或者其他通訊工具類,都可以,RPC并沒(méi)有規(guī)定說(shuō)你要用何種協(xié)議進(jìn)行通訊;
  • Stub通過(guò)調(diào)用通訊工具提供的方法,和Service B建立起了通訊,然后將請(qǐng)求數(shù)據(jù)發(fā)給Service B。需要注意的是,由于底層的網(wǎng)絡(luò)通訊是基于二進(jìn)制格式的,因此這里Stub傳給通訊工具類的數(shù)據(jù)也必須是二進(jìn)制,比如calculator.add(1,2),你必須把參數(shù)值1和2放到一個(gè)Request對(duì)象里頭(這個(gè)Request對(duì)象當(dāng)然不只這些信息,還包括要調(diào)用哪個(gè)服務(wù)的哪個(gè)RPC接口等其他信息),然后序列化為二進(jìn)制,再傳給通訊工具類,這一點(diǎn)也將在下面的代碼實(shí)現(xiàn)中體現(xiàn);
  • 二進(jìn)制的數(shù)據(jù)傳到Service B這一邊了,Service B當(dāng)然也有自己的通訊工具,通過(guò)這個(gè)通訊工具接收二進(jìn)制的請(qǐng)求;
  • 既然數(shù)據(jù)是二進(jìn)制的,那么自然要進(jìn)行反序列化了,將二進(jìn)制的數(shù)據(jù)反序列化為請(qǐng)求對(duì)象,然后將這個(gè)請(qǐng)求對(duì)象交給Service B的Stub處理;
  • 和之前的Service A的Stub一樣,這里的Stub也同樣是個(gè)“假玩意”,它所負(fù)責(zé)的,只是去解析請(qǐng)求對(duì)象,知道調(diào)用方要調(diào)的是哪個(gè)RPC接口,傳進(jìn)來(lái)的參數(shù)又是什么,然后再把這些參數(shù)傳給對(duì)應(yīng)的RPC接口,也就是Calculator的實(shí)際實(shí)現(xiàn)類去執(zhí)行。很明顯,如果是Java,那這里肯定用到了反射。
  • RPC接口執(zhí)行完畢,返回執(zhí)行結(jié)果,現(xiàn)在輪到Service B要把數(shù)據(jù)發(fā)給Service A了,怎么發(fā)?一樣的道理,一樣的流程,只是現(xiàn)在Service B變成了Client,Service A變成了Server而已:Service B反序列化執(zhí)行結(jié)果->傳輸給Service A->Service A反序列化執(zhí)行結(jié)果 -> 將結(jié)果返回給Application,完畢。

RPC需要考慮的問(wèn)題

要實(shí)現(xiàn)一個(gè)RPC不算難,難的是實(shí)現(xiàn)一個(gè)高性能高可靠的RPC框架。

比如,既然是分布式了,那么一個(gè)服務(wù)可能有多個(gè)實(shí)例,你在調(diào)用時(shí),要如何獲取這些實(shí)例的地址呢?

這時(shí)候就需要一個(gè)服務(wù)注冊(cè)中心,比如在Dubbo里頭,就可以使用Zookeeper作為注冊(cè)中心,在調(diào)用時(shí),從Zookeeper獲取服務(wù)的實(shí)例列表,再?gòu)闹羞x擇一個(gè)進(jìn)行調(diào)用。

那么選哪個(gè)調(diào)用好呢?這時(shí)候就需要負(fù)載均衡了,于是你又得考慮如何實(shí)現(xiàn)復(fù)雜均衡,比如Dubbo就提供了好幾種負(fù)載均衡策略。

這還沒(méi)完,總不能每次調(diào)用時(shí)都去注冊(cè)中心查詢實(shí)例列表吧,這樣效率多低呀,于是又有了緩存,有了緩存,就要考慮緩存的更新問(wèn)題,blablabla......

你以為就這樣結(jié)束了,沒(méi)呢,還有這些:

客戶端總不能每次調(diào)用完都干等著服務(wù)端返回?cái)?shù)據(jù)吧,于是就要支持異步調(diào)用;
服務(wù)端的接口修改了,老的接口還有人在用,怎么辦?總不能讓他們都改了吧?這就需要版本控制了;
服務(wù)端總不能每次接到請(qǐng)求都馬上啟動(dòng)一個(gè)線程去處理吧?于是就需要線程池;
服務(wù)端關(guān)閉時(shí),還沒(méi)處理完的請(qǐng)求怎么辦?是直接結(jié)束呢,還是等全部請(qǐng)求處理完再關(guān)閉呢?
......
如此種種,都是一個(gè)優(yōu)秀的RPC框架需要考慮的問(wèn)題。

可以直接使用現(xiàn)有的RPC框架,以Thrift為例。

Thrift 概述

Thrift最初由Facebook開(kāi)發(fā)的,后來(lái)提交給了Apache基金會(huì)將Thrift作為一個(gè)開(kāi)源項(xiàng)目。當(dāng)時(shí)facebook開(kāi)發(fā)使用它是為了解決系統(tǒng)中各系統(tǒng)間大數(shù)據(jù)量的傳輸通信以及系統(tǒng)之間語(yǔ)言環(huán)境不同需要跨平臺(tái)的特性,所以Thrift是支持跨語(yǔ)言,比如C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml都支持。Thrift是一個(gè)典型的CS結(jié)構(gòu),客戶端和服務(wù)端可以使用不同的語(yǔ)言開(kāi)發(fā)。既然客戶端和服務(wù)端能使用不同的語(yǔ)言開(kāi)發(fā),那么一定就要有一種中間語(yǔ)言來(lái)關(guān)聯(lián)客戶端和服務(wù)端的語(yǔ)言,沒(méi)錯(cuò),這種語(yǔ)言就是IDL(Interface Description Language)。

thrift-0.9.3.exe https://www.cnblogs.com/newboys/p/9366762.html

新建一個(gè)Thrift文件夾,將下載的thrift-0.9.1.exe重新命名為thrift.exe后放到Thrift文件夾下

配置環(huán)境變量,通過(guò)cmd thrift -version檢查是否配置成功

Thrift DEMO

1、編寫(xiě)Apple.thrift(thrift idl的用法可轉(zhuǎn)https://blog.csdn.net/u011642663/article/details/56015576

namespace java service.demo
service Apple{
    string appleString(1:string para);
    i32 appleAdd(1:i32 para);
    i32 appleMult(1:i32 para1,2:i32 para2);
}

2、使用thrift命令將idl轉(zhuǎn)為java

thrift -r -gen java Apple.thrift

3、將生成的Apple.java復(fù)制到項(xiàng)目中,如果有@Override異常,去掉即可。

4、編寫(xiě)實(shí)現(xiàn)類

public class AppleServiceImpl implements Apple.Iface {
    public String appleString(String para) throws TException {
        return "apple print hello " + para;
    }

    public int appleAdd(int para) throws TException {
        return para+10;
    }

    public int appleMult(int para1, int para2) throws TException {
        return para1 - para2;
    }
}

5、編寫(xiě)Server

public class AppleServiceServer {

    public static void main(String[] args) throws TTransportException {
        System.out.println("apple 服務(wù)端開(kāi)啟。。");
        TProcessor tprocessor = new Apple.Processor<Apple.Iface>(new AppleServiceImpl());
        TServerSocket serverTransport = new TServerSocket(9000);
        TServer.Args tArgs = new TServer.Args(serverTransport);
        tArgs.processor(tprocessor);
        tArgs.protocolFactory(new TBinaryProtocol.Factory());

        TServer server = new TSimpleServer(tArgs);
        server.serve();
    }
}

6、編寫(xiě)Client端

public class AppleServiceClient {

    public static void main(String[] args) {
        System.out.println("客戶端開(kāi)始。。");
        TTransport transport = null;
        try{
            transport = new TSocket("localhost",9000,3000);
            TProtocol protocol = new TBinaryProtocol(transport);
            Apple.Client client = new Apple.Client(protocol);
            transport.open();

            String result = client.appleString("abc");
            System.out.println("服務(wù)端返回  。  " + result);

            int a = client.appleAdd(8);
            int b = client.appleMult(29, 3);
            System.out.println("a= " + a + "  b="+b);

        }catch(TTransportException e){
            e.printStackTrace();
        }catch(TException e){
            e.printStackTrace();
        }finally{
            if(null!=transport){
                transport.close();
            }
        }
    }

}

7、運(yùn)行Server和Client


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

相關(guān)閱讀更多精彩內(nèi)容

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