天天看點

分布式計算程式設計模型之 RPC

遠端過程調用(rpc)範式的出現可以追溯到40年之前。時至今日,它仍是在編寫分布式應用時使用率最高的一種程式設計模型。隻是近些年來,人們對于rpc技術的質疑與批評聲逐漸多了起來。steve vinoski在2008年曾尖銳地指出,之是以rpc仍然能夠得到諸多開發者的支援,其原因隻有一個:舒适感!vinoski完全不認可這種思想,他表示:

“開發者的舒适感真的比正确性、可伸縮性、性能、關注分離、可擴充性以及附加的複雜性還要重要嗎?”

盡管面臨着這些尖銳的批評,但rpc的曆史地位是不容置疑的,而它在現代化的應用中仍能夠占據一席之地,成為分布式計算中一種重要的程式設計模型。正在攻讀博士學位的christopher meiklejohn近來開設了一系列部落格文章以回顧分布式計算中的各種程式設計模型與語言,在其中一篇文章中對rpc進行了詳盡的回顧與展望。

概述

簡單來說,一台機器上的程式對另一台機器上的子程式的調用就是一次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的調用-響應模型使得因系統過載而導緻消息無法正常處理時,對優先級的排列變得相當困難。

随後釋出的rfc 707草案繼承了rfc 684的思想,并提出了一個新問題,即各種服務,例如telnet與ftp之間的資源共享問題。因為這些服務各自具有不同的接口,是以使用者必須了解他們的操作端口與指令。該草案的作者提出了一個建議:為遠端過程的執行定義一個通用的接口,該接口接受一個參數清單,并依然遵循rpc的調用-響應模型。雖然這一提議并未解決rpc 684中所提出的問題,但這一模型在之後依然得到了許多系統的采納。

corba

corba是對面向對象語言的一種抽象,允許開發者進行跨機器、跨語言的通信。corba通過接口定義語言(idl)指定遠端對象的接口。idl用于生成遠端系統中的對象接口在本機中的樁代碼,并且在實際的語言實作與抽象接口之間生成映射關系。

corba的試圖為應用開發者帶來幾點益處:不依賴于具體的語言、作業系統以及架構;将idl中的抽象類型映射為具體實作所帶來的靜态類型特性;以及對象在不同機器之間的傳輸。corba承諾,通過使用映射,遠端方法調用的使用就與本地調用一樣簡單,甚至與分布式系統相關的異步也可被映射為本地異常進行處理。

但是,vinoski在2003年表示,僅基于透明性這一點對于程式設計語言與抽象進行評估的方式是有缺陷的。在他看來,idl映射的目的在于将中間件抽象直接合并至程式設計語言的領域中,通過這種透明性減少程式設計語言與中間件這兩者之間的阻抗失調。但問題在于,不恰當的透明性可能會掩蓋分布式計算中出現的某些問題,例如并發與部分失敗相關的問題。

對rpc範式的批評

tanenbaum與van renesse對rpc範式提出了尖銳的批評,他們認為将遠端調用與本地調用一視同仁的思想在本質上就是錯的,rpc試圖打造的透明性也是根本不可能實作的。他們認為為遠端通路專門設計一種協定是更好的做法。

tanenbaum與van renesse的批評意見涵蓋了rfc 684草案中已經提到的幾點内容:延遲、缺乏并行性、異常處理以及故障檢測等等。此外,他們還提出了一些批評意見:

單線程伺服器

如果伺服器無法立即向用戶端發送響應,比如它正在等待來自另一台伺服器的輸入。在這種情況下,不僅伺服器端産生了阻塞,用戶端也無法繼續執行本地計算過程。

兩軍問題

怎樣才能讓兩台伺服器對于某個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風格的語義能力。

====================================分割線================================

繼續閱讀