Atitit rpc之道 attilax著 艾龍 著 1. 遠端過程呼叫協議 2 2. RPC需要解決的問題: 組成部分 3 2.1. 通訊問題 : 3 2.2. 序列化 與 反序列化 : 3 2
Atitit rpc之道 attilax著 艾龍 著
- 遠端過程呼叫協議
同義詞 RPC一般指遠端過程呼叫協議
RPC(Remote Procedure Call)—遠端過程呼叫,它是一種通過網路從遠端計算機程式上請求服務,而不需要了解底層網路技術的協議。RPC協議假定某些傳輸協議的存在,如TCP或UDP,為通訊程式之間攜帶資訊資料。在OSI網路通訊模型中,RPC跨越了傳輸層和應用層。RPC使得開發包括網路分散式多程式在內的應用程式更加容易。
RPC採用客戶機/伺服器模式。請求程式就是一個客戶機,而服務提供程式就是一個伺服器。首先,客戶機呼叫程序傳送一個有程序引數的呼叫資訊到服務程序,然後等待應答資訊。在伺服器端,程序保持睡眠狀態直到呼叫資訊到達為止。當一個呼叫資訊到達,伺服器獲得程序引數,計算結果,傳送答覆資訊,然後等待下一個呼叫資訊,最後,客戶端呼叫程序接收答覆資訊,獲得程序結果,然後呼叫執行繼續進行。
有多種 RPC模式和執行。最初由 Sun 公司提出。IETF ONC 憲章重新修訂了 Sun 版本,使得 ONC RPC 協議成為 IETF 標準協議。現在使用最普遍的模式和執行是開放式軟體基礎的分散式計算環境(DCE)。
傳統的開發模式中,我們通常將系統的各個服務部署在單臺機器,隨著服務的擴充套件,這種方式已經完全無法滿足系統大規模的擴充套件需要,分散式系統由此誕生,在分散式系統中,最重要就是各個服務之間的 RPC 呼叫。
RPC 全稱 Remote Procedure Call——遠端過程呼叫,它是一種通過網路從遠端計算機程式上請求服務,而不需要了解底層網路技術的方式。簡單一點就是:通過一定協議和方法使得呼叫遠端計算機上的服務,就像呼叫本地服務一樣。
- RPC需要解決的問題: 組成部分
(可以稍作了解,詳情可檢視別的博文)
-
- 通訊問題 :
- 主要是通過在客戶端和伺服器之間建立TCP連線,遠端過程呼叫的所有交換的資料都在這個連線裡傳輸。連線可以是按需連線,呼叫結束後就斷掉,也可以是長連線,多個遠端過程呼叫共享同一個連線。
- 序列化 與 反序列化 :
- 當A伺服器上的應用發起遠端過程呼叫時,方法的引數需要通過底層的網路協議如TCP傳遞到B伺服器,由於網路協議是基於二進位制的,記憶體中的引數的值要序列化成二進位制的形式,也就是序列化(Serialize)或編組(marshal),通過定址和傳輸將序列化的二進位制傳送給B伺服器。
- 定址問題 :
- A伺服器上的應用怎麼告訴底層的RPC框架,如何連線到B伺服器(如主機或IP地址)以及特定的埠,方法的名稱名稱是什麼,這樣才能完成呼叫。比如基於Web服務協議棧的RPC,就要提供一個endpoint URI,或者是從UDDI服務上查詢。如果是RMI呼叫的話,還需要一個RMI Registry來註冊服務的地址。
- 歷史
模型 伺服器 程式設計 分散式 分散式系統
RPC機制的出現可以追溯到40年之前。
只是近些年來,人們對於RPC技術的質疑與批評聲逐漸多了起來。儘管面臨著這些尖銳的批評,但RPC的歷史地位是不容置疑的,而它在現代化的應用中仍能夠佔據一席之地,成為分散式計算中一種重要的程式設計模型。Christopher Meiklejohn近來開設了一系列部落格文章以講解分散式計算中的各種程式設計模型與語言,在其中一篇文章中對RPC進行了詳盡的回顧與展望。
遠端式呼叫(RPC)正規化的出現可以追溯到40年之前。
時至今日,它仍是在編寫分散式應用時使用率較高的一種程式設計模型。只是近些年來,人們對於RPC技術的質疑與批評聲逐漸多了起來。Steve Vinoski在2008年曾尖銳地指出,之所以RPC仍然能夠得到諸多開發者的支援,其原因只有一個:舒適感!Vinoski完全不認可這種思想,他表示:
簡單來說,一臺機器上的程式對另一臺機器上的子程式的呼叫就是一次RPC呼叫。在呼叫過程中,主程式不需要操心與遠端執行相關的任何程式碼,與本地呼叫相比,其區別就在於需要提供遠端節點的標識。最早為人所知並接受的RPC實現是由Sun提供的SunRPC機制,使用在其網路檔案系統(NFS)中。
除此之外,常見的RPC機制還包括Java的RMI、DCOM、XML-RPC、SOAP、CORBA,以及Google的gRPC等等。
-
- RPC的早期發展
RPC思想最早的原型可追溯至1974年所釋出的RFC 674草案 —— “過程呼叫協議文件第2版”,該草案當時的目標是為因特網上的全部70個節點定義一種共享資源的通用方式,在該草案中引入了過程呼叫範圍第2版(PCP)的概念。而在第二年釋出的RFC 684草案 —— “對以過程呼叫作為網路協議的評論”中,首次分析了RPC這種程式設計正規化存在的三大問題以及這些問題與分散式系統的本質問題之間的關聯。這三大問題可以簡要地概述如下:
過程呼叫通常是一種命令式操作,而命令式操作通常是一種來自底層抽象的非常快速的上下文切換操作。
本地呼叫與遠端呼叫的不同之處在於:遠端呼叫可能會產生延遲,甚至在產生故障時可能永遠也不會返回.
非同步的訊息傳遞,或是傳送某個訊息並等待響應是一種更理想的模型,因為這會使訊息的傳遞變得更加明確。
伴隨著這三大問題的是使用這種程式設計正規化時的一系列麻煩,這些麻煩在RPC的40多年發展歷史中始終陰魂不散,包括:如何從故障或錯誤中恢復;如何始終保證操作的正確順序;RPC正規化強制使用者進行同步程式設計方式;RPC的呼叫-響應模型使得因系統過載而導致訊息無法正常處理時,對優先順序的排列變得相當困難。
隨後釋出的RFC707草案繼承了RFC 684的思想,並提出了一個新問題,即各種服務,例如TELNET與FTP之間的資源共享問題。因為這些服務各自具有不同的介面,因此使用者必須瞭解他們的操作埠與命令。該草案的作者提出了一個建議:為遠端過程的執行定義一個通用的介面,該介面接受一個引數列表,並依然遵循RPC的呼叫-響應模型。雖然這一提議並未解決RPC 684中所提出的問題,但這一模型在之後依然得到了許多系統的採納
- RPC 的實現方式有很多,
- 可以基於常見的 HTTP 協議,
- 也可以在TCP上層封裝自定義協議,常見的 Web Service 就是基於 HTTP 協議的 RPC
,HTTP 協議的優點是具有良好的跨平臺性,特別適合異構系統較多的公司,但是由於 HTTP 報頭較為冗長,效能較差,基於 TCP 協議的 RPC 可以建立長連線,速度和效率明顯,但是難度和複雜程度很高。
RPC 的誕生讓構建分散式應用更容易,極大的擴大系統的可擴充套件性,容錯性。為複雜業務邏輯的系統進行服務化改造和高可用性升級提供了可能。
-
- Rest也是rpc的實現
- 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 呼叫,需要傳輸的最基本資料如下:
介面方法:包括介面的名字和相應的方法名字;
方法引數:包括引數的型別和取值;
附件引數,包括呼叫介面版本,介面超時時間等等。
因此,如果要自定義協議實現 RPC,我們必須再協議的訊息體中包含這部分資料,另外,我們需要定義一些協議元資料,這些元資料通常放在協議頭中,和包含必要引數的協議體一期組成了自定義訊息。
元資料通常會包含以下欄位,大部分欄位只需要1-2位:
magic: 魔數,方便協議解碼
header_size: 協議頭大小,便於解碼,同時可用用於處理TCP粘包問題
id :訊息 id,用來標示這次呼叫
version: 介面版本
type:訊息型別,可用包括普通呼叫訊息,心跳,控制訊息
status:訊息狀態,是否首次處理或者已經處理
body_size: 訊息體長度
serialize_type:訊息體序列化型別
body:具體訊息
- RPC這種程式設計正規化存在的三大問題以及這些問題
與分散式系統的本質問題之間的關聯。這三大問題可以簡要地概述如下:
-
- 過程呼叫通常是一種命令式操作,而命令式操作通常是一種來自底層抽象的非常快速的上下文切換操作。
-
- 本地呼叫與遠端呼叫的不同之處在於:遠端呼叫可能會產生延遲,甚至在產生故障時可能永遠也不會返回.
-
- 非同步的訊息傳遞,或是傳送某個訊息並等待響應是一種更理想的模型,因為這會使訊息的傳遞變得更加明確。
- 對RPC正規化的批評
Tanenbaum與van Renesse對RPC正規化提出了尖銳的批評,他們認為將遠端呼叫與本地呼叫一視同仁的思想在本質上就是錯的,RPC試圖打造的透明性也是根本不可能實現的。他們認為為遠端訪問專門設計一種協議是更好的做法。
Tanenbaum與van Renesse的批評意見涵蓋了RFC 684草案中已經提到的幾點內容:延遲、缺乏並行性、異常處理以及故障檢測等等。此外,他們還提出了一些批評意見:
單執行緒伺服器
如果伺服器無法立即向客戶端傳送響應,比如它正在等待來自另一臺伺服器的輸入。在這種情況下,不僅伺服器端產生了阻塞,客戶端也無法繼續執行本地計算過程。
兩軍問題
怎樣才能讓兩臺伺服器對於某個RPC的成功執行以及收到響應的結果達成一致呢?雖然某一方可以向對方傳送確認資訊,但對方還得向這個確認資訊傳送另一個確認資訊以再次確認。因此無論傳送幾次確認都無法實現100%的一致性。這一主題其實也是一致性問題的核心,許多與分散式系統相關的文獻對其進行了更深入的探討。
兩軍問題
怎樣才能讓兩臺伺服器對於某個RPC的成功執行以及收到響應的結果達成一致呢?雖然某一方可以向對方傳送確認資訊,但對方還得向這個確認資訊傳送另一個確認資訊以再次確認。因此無論傳送幾次確認都無法實現100%的一致性。這一主題其實也是一致性問題的核心,許多與分散式系統相關的文獻對其進行了更深入的探討。
引數
Tanenbaum與van Renesse也敘述了引數傳遞與引數封送的問題,這一問題在CORBA等有可能包含引用的物件系統中顯得更為嚴重。在這種情況下,為了保證引用的有效性,必須使用某種特定的分散式引用。
冪等性
最後一個問題是如何跨網路表達只執行一次的語義,作者在此處強調了冪等性(idempotence)的重要性。簡單來說,具有冪等性的操作即使經過多次執行,其結果與只執行一次也沒有區別。舉例來說,HTTP中的PUT就具有冪等性的語義,而POST則不具有這一語義。作者提到了一個可能發生的場景:假設伺服器在完成某個操作之後突然崩潰而來不及傳送確認資訊,客戶端就有可能在超時之後再次傳送這個實際上已經完成的請求,如果此時伺服器完成了重啟,就有可能再次執行這一操作。而如果該操作不滿足冪等性,就可能產生一些意外的副作用。
分散式計算備忘錄
Jim Waldo和Sam Kendall等人共同撰寫了一篇非常有名的論文“分散式計算備忘錄”,這篇論文在Reddit上被人推薦為“每個程式設計師都應當至少讀上兩篇”的論文。在這篇論文中,作者表示“忽略本地計算與分散式計算之間的區別是一種危險的思想”,特別指出了Emerald、Argus、DCOM以及CORBA的設計問題。作者將這些設計問題歸納為“三個錯誤的原則”:
“對於某個應用來說,無論它的部署環境如何,總有一種單一的、自然的面向物件設計可以符合其需求。”
“故障與效能問題與某個應用的元件實現直接相關,在最初的設計中無需考慮這些問題。”
“物件的介面與使用物件的上下文無關”
十年一輪迴的錯誤
Waldo表示,每過10年,人們就會再次嘗試將本地計算與遠端計算的設計揉合在一起,再一次犯下相同的錯誤。他再次強調:本地計算與遠端計算的本質是完全不同的。
延遲
最明顯的區別就在於延遲問題:如果忽略了延遲問題,軟體的效能就會受到直接影響。Waldo表示,“依賴於底層硬體速度的逐步提高”是錯誤的,一些實際的問題是很難通過測試找出的。效能分析是一個複雜的問題,在某一時刻表現良好的設計未必永遠是合適的。
記憶體訪問
Waldo對記憶體訪問的批評是特定於CORBA與它的繼任者的:物件可能會引用在同一地址空間內的指標,但一旦物件產生了移動,這些指標就會變得無效化。他認為處理這一問題的一種途徑是使用分散式共享記憶體,但在實踐上更常見的做法是使用封送或CORBA引用替換技術。
區域性故障
作者在最後談到了一個最本質的問題:區域性故障。在本地計算中,故障都是可檢測的。而在分散式計算中,相互獨立的元件可能會產生故障,並且故障可能是區域性的。
舒適感勝於正確性
在文章的開頭部分曾經提到了Vinoski對於RPC的批評,他認為選擇RPC的原因在於開發者的舒適感。在提出這一說法幾年之後,他提出了幾個非常重要的論點:
IDL的阻抗失調:對基礎型別進行對映可能比較簡單,但複雜的型別是非常難以對映的。
可伸縮性:RPC正規化本身並沒有對緩衝提供任何支援,或是提出任何緩解高延遲的機制,它仍然以一種偏命令式的操作構建分散式應用。
REST:REST本身是一種很好的思想,它為管理分散式資源的問題提出了特別的應對方式。但大多數基於REST打造的框架都改變了這一抽象思想,仍然重複了這一問題。
分散式程式語言
當我們在談到分散式程式語言時,多數開發者所想到的其實只是如何用一般性的程式語言去構建分散式系統。實際上,只要某種語言支援併發元素,並且能夠開啟一個網路套接字,那麼就能夠構建一個分散式系統。而真正的分散式程式語言為分散式特性提供了第一等的支援。像Go這樣的語言更像是一種併發語言,它為併發提供了第一等的支援。雖然併發是分散式中的一個重要部分,但他們畢竟還是不同的主題。
而Erlang則為分散式提供了第一等的支援,它雖然同樣使用了RPC機制,但更傾向於在程序之間使用非同步訊息傳遞方式。受到這一設計優秀表達能力的激勵,Distributed Process與Akka等框架也隨之出現,以提供Erlang風格的語義能力。
- RPC 與 微服務(MicroService)
微服務是一種架構思想,一個微服務一般只完成某個特定的功能,比如下單服務,訂單查詢服務,是將應用分解為小的,相互連線的服務。
微服務在系統層面有多種多樣的表現形式,例如暴露restful api,SOA服務或者http介面。RPC可以作為實現微服務系統的一種實現方式將各個應用都暴露出RPC的服務介面,從而實現微服務的架構。
-
- Msa實現靠rpc和restful
- REST是一種設計風格,它的很多思維方式與RPC是完全衝突的。
RPC的思想是把本地函式對映到API,也就是說一個API對應的是一個function,我本地有一個getAllUsers,遠端也能通過某種約定的協議來呼叫這個getAllUsers。至於這個協議是Socket、是HTTP還是別的什麼並不重要;
RPC中的主體都是動作,是個動詞,表示我要做什麼。
而REST則不然,它的URL主體是資源,是個名詞。而且也僅支援HTTP協議,規定了使用HTTP Method表達本次要做的動作,型別一般也不超過那四五種。這些動作表達了對資源僅有的幾種轉化方式。
這種設計思路是反程式設計師直覺的,因為在本地業務程式碼中仍然是一個個的函式,是動作,但表現在介面形式上則完全是資源的形式。
- RPC協議,包括PHPRPC、HPRose、JsonRPC以及Yar
- PHPRPC支援
PHPRPC 是一個輕型的、安全的、跨網際的、跨語言的、跨平臺的、跨環境的、跨域的、支援複雜物件傳輸的、支援引用引數傳遞的、支援內容輸出重定向的、支援分級錯誤處理的、支援會話的、面向服務的高效能遠端過程呼叫協議。目前該協議的最新版本為 3.0。詳細的資料可以參考phprpc官網( http://www.phprpc.org/zh_CN/)
ThinkPHP提供了對PHPRpc的服務端和客戶端呼叫的支援(客戶端是跨平臺跨語言的,可以支援任何語言的呼叫)。
-
- Hprose支援
Hprose (High Performance Remote Object Service Engine) 是一個MIT開源許可的新型輕量級跨語言跨平臺的面向物件的高效能遠端動態通訊中介軟體。它支援眾多語言,例如nodeJs, C++, .NET, Java, Delphi, Objective-C, ActionScript, JavaScript, ASP, PHP, Python, Ruby, Perl 等語言,通過 Hprose 可以在這些語言之間實現方便且高效的互通。
你可以認為它是 PHPRPC 的商業版本,但是它跟 PHPRPC 完全不同,hprose 協議是全新設計的,比 PHPRPC 更加高效,實現也完全是全部從頭開始的,比 PHPRPC 更加易用。更多資訊可以參考(http://www.hprose.com/)
ThinkPHP同樣也提供了對Hprose的服務端和客戶端呼叫的支援。
伺服器端的使用和PHPRPC的區別只是把控制器繼承Think\Controller\HproseController類即可,其他用法基本一致,例如
-
- JsonRPC支援
json-rpc是基於json的跨語言遠端呼叫協議,比xml-rpc、webservice等基於文字的協議傳輸資料格小;相對hessian、java-rpc等二進位制協議便於除錯、實現、擴充套件,是非常優秀的一種遠端呼叫協議。
ThinkPHP3.2提供了對JsonRPC的伺服器端和客戶端呼叫支援,伺服器端實現示例
Yar支援
Yar (yet another RPC framework) 是一個PHP擴充套件的RPC框架, 和現有的RPC框架(xml-rpc, soap)不同, 這是一個輕量級的框架, 支援多種打包協議(msgpack, json, php), 並且最重要的一個特點是, 它是可並行化的。
- 流行的RPC框架
目前流行的開源RPC框架還是比較多的。下面重點介紹三種:
- gRPC是Google最近公佈的開源軟體,基於最新的HTTP2.0協議,並支援常見的眾多程式語言。 我們知道HTTP2.0是基於二進位制的HTTP協議升級版本,目前各大瀏覽器都在快馬加鞭的加以支援。 這個RPC框架是基於HTTP協議實現的,底層使用到了Netty框架的支援。
- Thrift是Facebook的一個開源專案,主要是一個跨語言的服務開發框架。它有一個程式碼生成器來對它所定義的IDL定義檔案自動生成服務程式碼框架。使用者只要在其之前進行二次開發就行,對於底層的RPC通訊等都是透明的。不過這個對於使用者來說的話需要學習特定領域語言這個特性,還是有一定成本的。
- Dubbo是阿里集團開源的一個極為出名的RPC框架,在很多網際網路公司和企業應用中廣泛使用。協議和序列化框架都可以插拔是及其鮮明的特色。同樣 的遠端介面是基於Java Interface,並且依託於spring框架方便開發。可以方便的打包成單一檔案,獨立程序執行,和現在的微服務概念一致。
偷偷告訴你集團內部已經不怎麼使用dubbo啦,現在用的比較多的叫HSF,又名“好舒服”。後面有可能會開源,大家拭目以待。
- Rpc相對於rest http服務的好處
- HTTP服務
其實在很久以前,我對於企業開發的模式一直定性為HTTP介面開發,也就是我們常說的RESTful風格的服務介面。的確,對於在介面不多、系統與系統互動較少的情況下,解決資訊孤島初期常使用的一種通訊手段;優點就是簡單、直接、開發方便。利用現成的http協議進行傳輸。我們記得之前本科實習在公司做後臺開發的時候,主要就是進行介面的開發,還要寫一大份介面文件,嚴格地標明輸入輸出是什麼?說清楚每一個介面的請求方法,以及請求引數需要注意的事項等。比如下面這個例子:POST http://www.httpexample.com/restful/buyer/info/share介面可能返回一個JSON字串或者是XML文件。然後客戶端再去處理這個返回的資訊,從而可以比較快速地進行開發。但是對於大型企業來說,內部子系統較多、介面非常多的情況下,RPC框架的好處就顯示出來了,首先就是長連結,不必每次通訊都要像http一樣去3次握手什麼的,減少了網路開銷;其次就是RPC框架一般都有註冊中心,有豐富的監控管理;釋出、下線介面、動態擴充套件等,對呼叫方來說是無感知、統一化的操作
- RPC 模組詳解
下面我們根據上面的RPC的架構圖,對圖中的各個模組進行拆解,並解釋每個模組的作用。
服務端(Server):RPC 服務的提供者,負責將 RPC 服務匯出;
客戶端 (Client):RPC 服務的消費者,負責呼叫 RPC 服務;
代理(Proxy):通過動態代理,提供對遠端介面的代理實現;
執行器(Invoker):對於客戶端:主要負責服務呼叫的編碼,呼叫請求傳送和等待結果返回;對於服務方:負責處理呼叫邏輯並返回呼叫結果;
協議管理(Protocol):協議管理元件,負責整個 RPC 通訊協議的編/解碼;
連線埠(Connector):負責維持客戶方和服務方的長連線通道;
後臺處理(Processor):負責整個呼叫服務中的管理排程,包括執行緒池,分發,異常處理等;
連線通道(Channel):客戶端和伺服器端的資料傳輸通道。
具體到 JAVA 平臺來說,其中的3,4通常使用動態代理實現,5,6,7,8使用 NIO 或者一些高效能 NIO 框架,如 mina,netty 實現。
最簡單的 RPC JAVA 實現
在進一步拆解了元件並劃分了職責之後,這裡
- 參考資料
RPC是什麼 - BruceFeng.mhtml
RPC服務和HTTP服務對比 - CSDN部落格.mhtml