引言
RPC(Remote Procedure Call Protocol)——遠程過程調用協議,它是一種通過網絡從遠程計算機程序上請求服務,而不需要了解底層網絡技術的協議。RPC協議假定某些傳輸協議的存在,如TCP或UDP,為通信程序之間攜帶信息數據。在OSI網絡通信模型中,RPC跨越了傳輸層和應用層。RPC使得開發包括網絡分布式多程序在內的應用程序更加容易。
RPC采用客戶機/服務器模式。請求程序就是一個客戶機,而服務提供程序就是一個服務器。首先,客戶機調用進程發送一個有進程參數的調用信息到服務進程,然后等待應答信息。在服務器端,進程保持睡眠狀態直到調用信息到達為止。當一個調用信息到達,服務器獲得進程參數,計算結果,發送答復信息,然后等待下一個調用信息,最后,客戶端調用進程接收答復信息,獲得進程結果,然后調用執行繼續進行。有多種 RPC模式和執行。
RPC 結構
實現 RPC 的程序包括 5 個部分:
1. User
2. User-stub
3. RPCRuntime
4. Server-stub
5. Server
RPC 調用分類
RPC 調用的分類方式有很多種。
從通信協議層面可以分為:
基于 HTTP 協議的 RPC;
基于二進制協議的 RPC;
基于 TCP 協議的 RPC。
從是否跨平臺可分為:
單語言 RPC,如 RMI, Remoting;
跨平臺 RPC,如 google protobuffer, restful json,http XML。
從調用過程來看,可以分為同步通信RPC和異步通信RPC:
同步 RPC:指的是客戶端發起調用后,必須等待調用執行完成并返回結果;
異步 RPC:指客戶方調用后不關心執行結果返回,如果客戶端需要結果,可用通過提供異步 callback 回調獲取返回信息。大部分 RPC 框架都同時支持這兩種方式的調用。
RPC 功能目標
RPC 的主要功能目標是讓構建分布式計算(應用)更容易,在提供強大的遠程調用能力時不損失本地調用的語義簡潔性。為實現該目標,RPC 框架需提供一種透明調用機制讓使用者不必顯式的區分本地調用和遠程調用,在前文《淺出篇》中給出了一種實現結構,基于 stub 的結構來實現。下面我們將具體細化 stub 結構的實現。
Java RPC 實現
在進一步拆解了組件并劃分了職責之后,這里以一個最簡單 Java RPC 框架實現為例,對 RPC 具體邏輯進行分析。
RPC 框架服務發布代碼:





服務端發布服務的代碼如上,首先校驗傳入的端口和服務是否合法,然后開啟一個 socket 監聽,這兒為了簡便,沒有采用 NIO 方式,同時直接采用 java 的序列化方式,將傳入的數據通過反射取出調用的方法和參數,本地執行后將運行結果通過 socket 套接字返回給客戶端。RPC 框架服務調用代碼:框架中客戶端調用的代碼中,首先校驗對應的端口和主機是否合法,然后通過動態代理生成一個代理對象,在代理對象的方法中,攔截調用,通過建立 socket 連接,將方法和參數傳遞到遠端執行并獲取遠程執行返回結果。
RPC 調用測試:如上圖所示,服務器端發布一個接口服務 HelloService,客戶端成功通過 RPC 調用。
怎么做到遠程服務調用?
怎么封裝通信細節才能讓用戶像以本地調用方式調用遠程服務呢?對java來說就是使用代理!java代理有兩種方式:1) jdk 動態代理;2)字節碼生成。盡管字節碼生成方式實現的代理更為強大和高效,但代碼不易維護,大部分公司實現RPC框架時還是選擇動態代理方式。
下面簡單介紹下動態代理怎么實現我們的需求。我們需要實現RPCProxyClient代理類,代理類的invoke方法中封裝了與遠端服務通信的細節,消費方首先從RPCProxyClient獲得服務提供方的接口,當執行helloWorldService.sayHello(“test”)方法時就會調用invoke方法。
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
publicclassRPCProxyClient implementsjava.lang.reflect.InvocationHandler{
privateObjectobj;
publicRPCProxyClient(Objectobj){
this.obj=obj;
}
/**
* 得到被代理對象;
*/
publicstaticObjectgetProxy(Objectobj){
returnjava.lang.reflect.Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),newRPCProxyClient(obj));
}
/**
* 調用此方法執行
*/
publicObjectinvoke(Objectproxy,Method method,Object[]args)
throwsThrowable{
//結果參數;
Objectresult=newObject();
// ...執行通信相關邏輯
// ...
returnresult;
}
}
Java
1
2
3
4
5
6
publicclassTest{
publicstaticvoidmain(String[]args){
HelloWorldService helloWorldService=(HelloWorldService)RPCProxyClient.getProxy(HelloWorldService.class);
helloWorldService.sayHello("test");
}
}
異常處理
無論 RPC 怎樣努力把遠程調用偽裝的像本地調用,但它們依然有很大的不同點,而且有一些異常情況是在本地調用時絕對不會碰到的。在說異常處理之前,我們先比較下本地調用和 RPC調用的一些差異:
- 本地調用一定會執行,而遠程調用則不一定,調用消息可能因為網絡原因并未發送到服務方。
- 本地調用只會拋出接口聲明的異常,而遠程調用還會拋出 RPC 框架運行時的其他異常。
- 本地調用和遠程調用的性能可能差距很大,這取決于 RPC 固有消耗所占的比重。
正是這些區別決定了使用 RPC 時需要更多考量。
當調用遠程接口拋出異常時,異常可能是一個業務異常, 也可能是 RPC 框架拋出的運行時異常(如:網絡中斷等)。
業務異常表明服務方已經執行了調用,可能因為某些原因導致未能正常執行, 而 RPC 運行時異常則有可能服務方根本沒有執行,對調用方而言的異常處理策略自然需要區分。
由于 RPC 固有的消耗相對本地調用高出幾個數量級,
本地調用的固有消耗是納秒級,而 RPC 的固有消耗是在毫秒級。
那么對于過于輕量的計算任務就并不合適導出遠程接口由獨立的進程提供服務,
只有花在計算任務上時間遠遠高于 RPC 的固有消耗才值得導出為遠程接口提供服務。
總結
以 上就是我對Java開發大型互聯網RPC遠程調用服務實現之問題處理方案 問題及其優化總結,分享給大家,覺得收獲的話可以點個關注收藏轉發一波喔,謝謝大佬們支持!
最后,每一位讀到這里的網友,感謝你們能耐心地看完。希望在成為一名更優秀的Java程序員的道路上,我們可以一起學習、一起進步!都能贏取白富美,走向架構師的人生巔峰!
想了解學習Java方面的技術內容以及Java技術視頻的內容可加群:722040762 驗證碼:頭條(06 必過)歡迎大家的加入喲!
