一個陽光明媚的早晨,老婆又在翻看我訂閱的技術(shù)雜志。
“老公,什么是RPC呀,為什么你們程序員那么多黑話!”,老婆還是一如既往的好奇。
“RPC,就是Remote Procedure Call的簡稱呀,翻譯成中文就是遠程過程調(diào)用嘛”,我一邊看著書,一邊漫不經(jīng)心的回答著。
“啥?你在說啥?誰不知道翻譯成中文是什么意思?你個廢柴,快給我滾去洗碗!”
“我去。。?!?,我如夢初醒,我對面坐著的可不是一個程序員,為了不去洗碗,我瞬間調(diào)動起全部腦細胞,星辰大海在我腦中匯聚,靈感涌現(xiàn)......
"是這樣,遠程過程調(diào)用,自然是相對于本地過程調(diào)用來說的嘛。"
“嗯哼,那先給老娘講講,本地過程調(diào)用是啥子?”
“本地過程調(diào)用,就好比你現(xiàn)在在家里,你要想洗碗,那你直接把碗放進洗碗機,打開洗碗機開關(guān)就可以洗了。這就叫本地過程調(diào)用?!?/p>
“哎呦,我可不干,那啥是遠程過程調(diào)用?”
“遠程嘛,那就是你現(xiàn)在不在家,跟姐妹們浪去了,突然發(fā)現(xiàn)碗還沒洗,打了個電話過來,叫我去洗碗,這就是遠程過程調(diào)用啦”,多么通俗易懂的解釋,我真是天才!
“哦!我明白了”,說著,老婆開始收拾包包。
“你這是干啥去哦”
“我?我要出門浪去呀,待會記得接收我的遠程調(diào)用哦,哦不,咱們要專業(yè)點,應(yīng)該說,待會記得接收我的RPC哦!”
......
非程序員請就此止步,程序員請繼續(xù)往前走......

如何科學(xué)的解釋RPC
說起RPC,就不能不提到分布式,這個促使RPC誕生的領(lǐng)域。
假設(shè)你有一個計算器接口,Calculator,以及它的實現(xiàn)類CalculatorImpl,那么在系統(tǒng)還是單體應(yīng)用時,你要調(diào)用Calculator的add方法來執(zhí)行一個加運算,直接new一個CalculatorImpl,然后調(diào)用add方法就行了,這其實就是非常普通的本地函數(shù)調(diào)用,因為在同一個地址空間,或者說在同一塊內(nèi)存,所以通過方法棧和參數(shù)棧就可以實現(xiàn)。

現(xiàn)在,基于高性能和高可靠等因素的考慮,你決定將系統(tǒng)改造為分布式應(yīng)用,將很多可以共享的功能都單獨拎出來,比如上面說到的計算器,你單獨把它放到一個服務(wù)里頭,讓別的服務(wù)去調(diào)用它。

這下問題來了,服務(wù)A里頭并沒有CalculatorImpl這個類,那它要怎樣調(diào)用服務(wù)B的CalculatorImpl的add方法呢?
有同學(xué)會說,可以模仿B/S架構(gòu)的調(diào)用方式呀,在B服務(wù)暴露一個Restful接口,然后A服務(wù)通過調(diào)用這個Restful接口來間接調(diào)用CalculatorImpl的add方法。
很好,這已經(jīng)很接近RPC了,不過如果是這樣,那每次調(diào)用時,是不是都需要寫一串發(fā)起http請求的代碼呢?比如httpClient.sendRequest...之類的,能不能像本地調(diào)用一樣,去發(fā)起遠程調(diào)用,讓使用者感知不到遠程調(diào)用的過程呢,像這樣:
@Reference
private Calculator calculator;
...
calculator.add(1,2);
...
這時候,有同學(xué)就會說,用代理模式呀!而且最好是結(jié)合Spring IoC一起使用,通過Spring注入calculator對象,注入時,如果掃描到對象加了@Reference注解,那么就給它生成一個代理對象,將這個代理對象放進容器中。而這個代理對象的內(nèi)部,就是通過httpClient來實現(xiàn)RPC遠程過程調(diào)用的。
可能上面這段描述比較抽象,不過這就是很多RPC框架要解決的問題和解決的思路,比如阿里的Dubbo。
總結(jié)一下,RPC要解決的兩個問題:
- 解決分布式系統(tǒng)中,服務(wù)之間的調(diào)用問題。
- 遠程調(diào)用時,要能夠像本地調(diào)用一樣方便,讓調(diào)用者感知不到遠程調(diào)用的邏輯。
如何實現(xiàn)一個RPC
實際情況下,RPC很少用到http協(xié)議來進行數(shù)據(jù)傳輸,畢竟我只是想傳輸一下數(shù)據(jù)而已,何必動用到一個文本傳輸?shù)膽?yīng)用層協(xié)議呢,我為什么不直接使用二進制傳輸?比如直接用Java的Socket協(xié)議進行傳輸?
不管你用何種協(xié)議進行數(shù)據(jù)傳輸,一個完整的RPC過程,都可以用下面這張圖來描述:

以左邊的Client端為例,Application就是rpc的調(diào)用方,Client Stub就是我們上面說到的代理對象,也就是那個看起來像是Calculator的實現(xiàn)類,其實內(nèi)部是通過rpc方式來進行遠程調(diào)用的代理對象,至于Client Run-time Library,則是實現(xiàn)遠程調(diào)用的工具包,比如jdk的Socket,最后通過底層網(wǎng)絡(luò)實現(xiàn)實現(xiàn)數(shù)據(jù)的傳輸。
這個過程中最重要的就是序列化和反序列化了,因為數(shù)據(jù)傳輸?shù)臄?shù)據(jù)包必須是二進制的,你直接丟一個Java對象過去,人家可不認識,你必須把Java對象序列化為二進制格式,傳給Server端,Server端接收到之后,再反序列化為Java對象。
下一次我也將通過代碼,給大家演示一下,如何實現(xiàn)一個簡單的RPC。
RPC vs Restful
其實這兩者并不是一個維度的概念,總得來說RPC涉及的維度更廣。
如果硬要比較,那么可以從RPC風(fēng)格的url和Restful風(fēng)格的url上進行比較。
比如你提供一個查詢訂單的接口,用RPC風(fēng)格,你可能會這樣寫:
/queryOrder?orderId=123
用Restful風(fēng)格呢?
Get
/order?orderId=123
RPC是面向過程,Restful是面向資源,并且使用了Http動詞。從這個維度上看,Restful風(fēng)格的url在表述的精簡性、可讀性上都要更好。
RPC vs RMI
嚴格來說這兩者也不是一個維度的。
RMI是Java提供的一種訪問遠程對象的協(xié)議,是已經(jīng)實現(xiàn)好了的,可以直接用了。
而RPC呢?人家只是一種編程模型,并沒有規(guī)定你具體要怎樣實現(xiàn),你甚至都可以在你的RPC框架里面使用RMI來實現(xiàn)數(shù)據(jù)的傳輸,比如Dubbo:Dubbo - rmi協(xié)議
RPC沒那么簡單
要實現(xiàn)一個RPC不算難,難的是實現(xiàn)一個高性能高可靠的RPC框架。
比如,既然是分布式了,那么一個服務(wù)可能有多個實例,你在調(diào)用時,要如何獲取這些實例的地址呢?
這時候就需要一個服務(wù)注冊中心,比如在Dubbo里頭,就可以使用Zookeeper作為注冊中心,在調(diào)用時,從Zookeeper獲取服務(wù)的實例列表,再從中選擇一個進行調(diào)用。
那么選哪個調(diào)用好呢?這時候就需要負載均衡了,于是你又得考慮如何實現(xiàn)復(fù)雜均衡,比如Dubbo就提供了好幾種負載均衡策略。
這還沒完,總不能每次調(diào)用時都去注冊中心查詢實例列表吧,這樣效率多低呀,于是又有了緩存,有了緩存,就要考慮緩存的更新問題,blablabla......
你以為就這樣結(jié)束了,沒呢,還有這些:
- 客戶端總不能每次調(diào)用完都干等著服務(wù)端返回數(shù)據(jù)吧,于是就要支持異步調(diào)用;
- 服務(wù)端的接口修改了,老的接口還有人在用,怎么辦?總不能讓他們都改了吧?這就需要版本控制了;
- 服務(wù)端總不能每次接到請求都馬上啟動一個線程去處理吧?于是就需要線程池;
- 服務(wù)端關(guān)閉時,還沒處理完的請求怎么辦?是直接結(jié)束呢,還是等全部請求處理完再關(guān)閉呢?
- ......
如此種種,都是一個優(yōu)秀的RPC框架需要考慮的問題。
當(dāng)然,接下來我們還是先實現(xiàn)一個簡單的RPC,再在上面一步步優(yōu)化!
轉(zhuǎn)自: 如何實現(xiàn)一個簡單的RPC