如何給老婆解釋什麼是RPC

一個陽光明媚的早晨,老婆又在翻看我訂閱的技術雜誌。

“老公,什麼是RPC呀,爲什麼你們程序員那麼多黑話!”,老婆還是一如既往的好奇。
“RPC,就是Remote Procedure Call的簡稱呀,翻譯成中文就是遠程過程調用嘛”,我一邊看着書,一邊漫不經心的回答着。
“啥?你在說啥?誰不知道翻譯成中文是什麼意思?你個廢柴,快給我滾去洗碗!”
“我去。。。”,我如夢初醒,我對面坐着的可不是一個程序員,爲了不去洗碗,我瞬間調動起全部腦細胞,星辰大海在我腦中匯聚,靈感涌現......

"是這樣,遠程過程調用,自然是相對於本地過程調用來說的嘛。"
“嗯哼,那先給老孃講講,本地過程調用是啥子?”
“本地過程調用,就好比你現在在家裏,你要想洗碗,那你直接把碗放進洗碗機,打開洗碗機開關就可以洗了。這就叫本地過程調用。”

“哎呦,我可不幹,那啥是遠程過程調用?”
“遠程嘛,那就是你現在不在家,跟姐妹們浪去了,突然發現碗還沒洗,打了個電話過來,叫我去洗碗,這就是遠程過程調用啦”,多麼通俗易懂的解釋,我真是天才!

“哦!我明白了”,說着,老婆開始收拾包包。
“你這是幹啥去哦”
“我?我要出門浪去呀,待會記得接收我的遠程調用哦,哦不,咱們要專業點,應該說,待會記得接收我的RPC哦!”
......

非程序員請就此止步,程序員請繼續往前走......

7143349-b927e065a62740f2.png

如何科學的解釋RPC

說起RPC,就不能不提到分佈式,這個促使RPC誕生的領域。

假設你有一個計算器接口,Calculator,以及它的實現類CalculatorImpl,那麼在系統還是單體應用時,你要調用Calculator的add方法來執行一個加運算,直接new一個CalculatorImpl,然後調用add方法就行了,這其實就是非常普通的本地函數調用,因爲在同一個地址空間,或者說在同一塊內存,所以通過方法棧和參數棧就可以實現。

7143349-443a8cbcf6136ef5.png

現在,基於高性能和高可靠等因素的考慮,你決定將系統改造爲分佈式應用,將很多可以共享的功能都單獨拎出來,比如上面說到的計算器,你單獨把它放到一個服務裏頭,讓別的服務去調用它。

7143349-9b1fbd80b8db018b.png

這下問題來了,服務A裏頭並沒有CalculatorImpl這個類,那它要怎樣調用服務B的CalculatorImpl的add方法呢?

有同學會說,可以模仿B/S架構的調用方式呀,在B服務暴露一個Restful接口,然後A服務通過調用這個Restful接口來間接調用CalculatorImpl的add方法。

很好,這已經很接近RPC了,不過如果是這樣,那每次調用時,是不是都需要寫一串發起http請求的代碼呢?比如httpClient.sendRequest...之類的,能不能像本地調用一樣,去發起遠程調用,讓使用者感知不到遠程調用的過程呢,像這樣:

@Reference
private Calculator calculator;

...

calculator.add(1,2);

...

這時候,有同學就會說,用代理模式呀!而且最好是結合Spring IoC一起使用,通過Spring注入calculator對象,注入時,如果掃描到對象加了@Reference註解,那麼就給它生成一個代理對象,將這個代理對象放進容器中。而這個代理對象的內部,就是通過httpClient來實現RPC遠程過程調用的。

可能上面這段描述比較抽象,不過這就是很多RPC框架要解決的問題和解決的思路,比如阿里的Dubbo。

總結一下,RPC要解決的兩個問題:

  1. 解決分佈式系統中,服務之間的調用問題。
  2. 遠程調用時,要能夠像本地調用一樣方便,讓調用者感知不到遠程調用的邏輯。

如何實現一個RPC

實際情況下,RPC很少用到http協議來進行數據傳輸,畢竟我只是想傳輸一下數據而已,何必動用到一個文本傳輸的應用層協議呢,我爲什麼不直接使用二進制傳輸?比如直接用Java的Socket協議進行傳輸?

不管你用何種協議進行數據傳輸,一個完整的RPC過程,都可以用下面這張圖來描述

7143349-9e00bb104b9e3867.png

以左邊的Client端爲例,Application就是rpc的調用方,Client Stub就是我們上面說到的代理對象,也就是那個看起來像是Calculator的實現類,其實內部是通過rpc方式來進行遠程調用的代理對象,至於Client Run-time Library,則是實現遠程調用的工具包,比如jdk的Socket,最後通過底層網絡實現實現數據的傳輸。

這個過程中最重要的就是序列化反序列化了,因爲數據傳輸的數據包必須是二進制的,你直接丟一個Java對象過去,人家可不認識,你必須把Java對象序列化爲二進制格式,傳給Server端,Server端接收到之後,再反序列化爲Java對象。

下一次我也將通過代碼,給大家演示一下,如何實現一個簡單的RPC。

RPC vs Restful

其實這兩者並不是一個維度的概念,總得來說RPC涉及的維度更廣。

如果硬要比較,那麼可以從RPC風格的url和Restful風格的url上進行比較。

比如你提供一個查詢訂單的接口,用RPC風格,你可能會這樣寫:

/queryOrder?orderId=123

用Restful風格呢?

Get  
/order?orderId=123

RPC是面向過程,Restful是面向資源,並且使用了Http動詞。從這個維度上看,Restful風格的url在表述的精簡性、可讀性上都要更好。

RPC vs RMI

嚴格來說這兩者也不是一個維度的。

RMI是Java提供的一種訪問遠程對象的協議,是已經實現好了的,可以直接用了。

而RPC呢?人家只是一種編程模型,並沒有規定你具體要怎樣實現,你甚至都可以在你的RPC框架裏面使用RMI來實現數據的傳輸,比如Dubbo:Dubbo - rmi協議

RPC沒那麼簡單

要實現一個RPC不算難,難的是實現一個高性能高可靠的RPC框架。

比如,既然是分佈式了,那麼一個服務可能有多個實例,你在調用時,要如何獲取這些實例的地址呢?

這時候就需要一個服務註冊中心,比如在Dubbo裏頭,就可以使用Zookeeper作爲註冊中心,在調用時,從Zookeeper獲取服務的實例列表,再從中選擇一個進行調用。

那麼選哪個調用好呢?這時候就需要負載均衡了,於是你又得考慮如何實現複雜均衡,比如Dubbo就提供了好幾種負載均衡策略。

這還沒完,總不能每次調用時都去註冊中心查詢實例列表吧,這樣效率多低呀,於是又有了緩存,有了緩存,就要考慮緩存的更新問題,blablabla......

你以爲就這樣結束了,沒呢,還有這些:

  • 客戶端總不能每次調用完都乾等着服務端返回數據吧,於是就要支持異步調用;
  • 服務端的接口修改了,老的接口還有人在用,怎麼辦?總不能讓他們都改了吧?這就需要版本控制了;
  • 服務端總不能每次接到請求都馬上啓動一個線程去處理吧?於是就需要線程池;
  • 服務端關閉時,還沒處理完的請求怎麼辦?是直接結束呢,還是等全部請求處理完再關閉呢?
  • ......

如此種種,都是一個優秀的RPC框架需要考慮的問題。

當然,接下來我們還是先實現一個簡單的RPC,再在上面一步步優化!

傳送門: 如何實現一個簡單的RPC

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章