古老又有生命力的RPC
RPC (Remote Procedure Call,遠端過程呼叫)是建立在Socket之上的一種多程序間的通訊機制。不同於複雜的Socket通訊方式,RPC的初心是設計一套遠端通訊的通用框架,這個框架能夠自動處理通訊協議、物件序列化、網路傳輸等複雜細節,並且希望開發者在使用這個框架以後,呼叫一個遠端機器上的介面的程式碼與以本地方法呼叫的程式碼“看起來沒什麼區別”,從而大大減小分散式系統的開發難度,使得比較容易開發分散式系統。
為了便於理解Socket通訊與RPC通訊在程式設計方面的區別,我們舉個簡單的例子來解釋:假設目前在B機器上有一個程序,可以簡單地實現四則運算,比如我們輸入 1+1讓它計算並返回計算結果,那麼用Socket開發時,客戶端的虛擬碼大致如下:
client =new Socket (B);client.write("plus(1,1)");result=client.read();client.close();
而服務端的虛擬碼大致如下:
socketServer server=new ServerSocket ();server.listen ();while(true){cmd-server.read();if(cmd .startwith("plus(")){··client.write(result);}}
上述程式碼僅為大量簡化後的虛擬碼,如果要達到生產質量的要求,則還需要考慮如下複雜問題。
網路異常問題:在呼叫過程中如果發生網路異常,則呼叫失敗,客戶端需要明確知道發生了異常,然後有針對性地進行處理。複雜資料傳輸過程中的編碼和解碼問題:當輸入引數或者輸出引數很複雜時,引數編碼及解碼過程中的複雜性經常會讓思維不夠嚴密的程式設計師頭腦“短路”。客戶端的連線複用問題,如果每次呼叫都建立一個TCP連線,用完關閉,那麼呼叫性會很低,因為將大量時間都用在TCP建立連線的過程中了,因此客戶端需要一種連線保持及連線複用的機制,還涉及服務端與客戶端連線心跳檢測及超時機制等相關的複雜問題。服務端需要有多執行緒機制來應對客戶端的併發請求,以提升效能。所以你會發現,即使我們有了Socket,有了好的NIO框架,也基本上沒有多少人能開發出一個基於Socket的高質量的遠端通訊模組,而隨便一個分散式系統就有很多遠端通訊的功能點,如此一來,開發一個分散式系統仍然是一件很難的事。
於是,分散式系統中最重要的一個開發框架誕生了,這就是大名鼎鼎的RPC。RPC最初由Sun公司提出,即 Sun RPC,後來也成為IETF國際標準,至今仍然重要的NFS協議就是最早的基於RPC的一個重要案例。
為了將一個傳統的程式改寫成RPC程式,我們要在程式里加入另外一些程式碼,這個過程叫作Stub。我們可以想象一個傳統程式,它的一個程序被轉移到一個遠端機器中,在客戶端及服務端分別有一個Stub模組實現了遠端過程呼叫所需要的通訊功能,比如引數及呼叫結果的序列化功能,並透過網路完成遠端傳輸,因為Stub與原來的Server端使用了同樣的介面,因此增加這些Stub 程式碼既不需要更改原來Client端的呼叫邏輯,也不需要更改Server端的邏輯程式碼,這個過程如下圖所示。
整個RPC的呼叫流程如下。
(1)服務消費方(Client)以本地呼叫方式呼叫服務。
(2)Client Stub在接收到呼叫後負責將方法、引數等組裝成能夠進行網路傳輸的訊息體。(3)Client Stub找到服務地址,並將訊息傳送到服務端。
( 4)Server Stub在收到訊息後進行解碼。
( 5 )Server Stub根據解碼結果呼叫本地服務。(6)本地服務執行並將結果返回給Server Stub。
(7) Server Stub將返回結果打包成訊息併發送到消費方。(8)Client Stub接收到訊息並進行解碼。
(9)服務消費方得到最終結果。
要實現一個完整的RPC 架構,就需要如下專有技術。
高效能網路程式設計技術。物件(複雜資料結構)序列化與反序列化技術。自動程式碼生成或者動態代理程式設計技術。比如Java裡經典的RPC實現方案RMI,就用到了Java 預設的序列化機制和動態代理程式設計技術。不過,Java裡的RMI及其他語言裡特定的RPC架構大多存在一個很明顯的缺陷,即僅限於本語言的客戶端呼叫,換種語言就無法呼叫了。而開發需要支援多語言的RPC架構,其難度至少提升了一個數量級。
規範巨大而複雜:許多特性都未曾實現,甚至概念性的證明都沒有做過;有些技術特性根本不可能實現,即使實現,也無法提供可移植性。CORBA很難學習:平臺的學習曲線陡峭,技術複雜,不容易正確使用,這些因素導致開發週期長、易出錯。早期的實現常常充滿 Bug 並且缺乏有質量的文件,有經驗的CORBA程式設計師稀缺。程式設計開發過於複雜:有經驗的CORBA開發者發現編寫實用的CORBA應用程式相當困難。許多API 都很複雜、不一致,甚至讓人感覺神秘,使得開發者必須關注許多細節問題。相比之下,元件模型的簡單性,例如同時代的EJB,使得程式設計簡單很多。費用昂貴:在使用商用CORBA 產品時,開發者一般都需要花費幾千美元購買開發者License,此外,部署CORBA產品與部署Oracle資料庫一樣,還需要客戶支付企業License費用,而且這個費用很可能與部署在CORBA平臺上的應用數量掛鉤,因此對很多潛在的客戶來說,CORBA這樣的平臺太昂貴了。Sun 與Java成為COBRA最大的競爭對手:商業公司轉向了Sun的Java與新興的Web,並且開始構建基於Web瀏覽器、Java和EJB的電子商務基礎設施。XML技術的興起加速了COBRA 的沒落:20世紀90年代後期,XML成為計算機工業新的銀彈,幾乎被定義為XML的事物都是好的。在放棄了DCOM 之後,微軟並沒有把電子商務市場留給競爭對手,沒有再參與一場不可能打贏的戰爭,而是使用XML開闢了新的戰場。1999年年底,工業界看到了SOAP的釋出。SOAP由微軟和DevelopMentor 釋出,隨後提交給W3C作為標準。SOAP使用XML作為RPC新的物件序列化機制,IBM則又繼續發揚光大這條路線,推出Web Service等整套方案。SOAP在嚴格意義上是屬於XML-RPC (XML Remote Procedure Call)技術的一個變種,一個XML-RPC請求訊息就是一個HTTP-POST 請求訊息,其請求訊息主體基於XML格式。客戶端傳送XML-RPC 請求訊息到服務端,呼叫服務端的遠端方法並在服務端執行遠端方法。遠端方法在執行完畢後返回響應訊息給客戶端,其響應訊息主體同樣基於XML格式。遠端方法的引數支援數字、字串、日期等,也支援列表陣列和其他複雜結構型別。SOAP也是第一次真正成功地解決了多語言多平臺支援的開放性RPC標準。
一個SOAP請求報文例項(查詢股票價格)如下:
<?xml version="1.0"?><soap :Envelopexmlns:soap="http://www.w3.org/2001/12/soap-envelope"soap:encodingStyle="http://www .w3.org/2001/12/soap-encoding"><soap:Body xmlns:m="http://www.example.org/stock"><m:GetStockPrice><m: StockName>IBM</m:StockName></m:GetStockPrice></soap:Body></soap :Envelope>
對應的應答報文例項如下:
<?xml version="l.0"2><soap:Envelopexmlns:soap="http://www.w3.org/2001/12/soap-envelope"soap:encodingstyle="http:/ /www .w3.org/2001/12/soap-encoding"><soap:Body xmlns:m="http://www.example.org/stock"><m :GetStockPriceResponse><m: Price>34.5</m:Price></m: GetStockPriceResponse></soap:Body></soap:Envelope>
我們看到,SOAP 的報文很複雜而且編碼臃腫,由於它是面向機器識別的表達格式,所以程式設計師很難直接理解它的報文,該缺陷最終導致了SOAP的末路與HTTP REST的通訊方式的興起。HTTP REST採用了讓人容易理解的JSON格式來傳遞請求與應答引數,因而開發更為方便,但HTTP REST已經脫離了RPC的範疇,最明顯的幾個特徵:它無須客戶端Stub程式碼與服務端Stub 程式碼,呼叫也不再類似於本地方法呼叫方式了。
在RPC的路線演化過程中雖然意外地產生了HTTP REST這個慢慢侵佔了RPC大部分應用領地的“異類”,並且導致了一度盛行的XML-RPC的“滅絕”,但同時推動正統RPC技術走向一個新的發展階段,追求更高的效能及增加對多語言多平臺的支援,成為越來越多的開源RPC架構的目標。其典型的代表為Thrift、Avro 等新生的開源框架,這些框架在大資料系統、大型分散式系統及移動網際網路應用方面被越來越多的公司使用。
之後,最初參與CORBA的技術專家們打造了延續至今的RPC平臺——ZeroC lce。現在,ZeroC Ice已經成為一個很強大的微服務架構平臺,很適合作為大型分散式系統、電商系統、電信金融等關鍵業務系統的基礎架構。
RPC技術發展至今,雖然是相對古老的傳統技術,卻有著其獨特的優勢,特別是擁有高效能傳輸及支援高併發請求的絕對優勢,使得RPC技術在網際網路時代又一次被巨頭們所重視。
其中一個典型的代表是Facebook開源的跨語言的RPC架構Thrift。Thrift 於2008年被貢獻給Apache,目前支援多達25種程式語言。Thrift 與ZeroC Ice屬於COBRA一脈相傳的“很正統”的RPC實現方案,使用方式也很類似,即先編寫服務介面的IDL檔案,然後利用框架提供的編譯生成器工具自動生成Server端的骨架程式碼和客戶端的呼叫程式碼,最後由程式設計師填充骨架程式碼。
另一個典型的代表是谷歌於2015年開源的跨語言的RPC框架——gRPC,gRPC採用的預設的編碼機制也是谷歌設計的ProtoBuf。gRPC支援在任意環境下使用,支援物聯網、手機、瀏覽器。支援瀏覽器這一點很關鍵,它表明gRPC 的定位及與傳統RPC的不同。gRPC沒有基於傳統的自定義TCP Socket傳輸通道,而是基於現有的HTTP 2.0!這樣看來,gRPC的效能肯定比不過ZeroC Ice、Thrift這些傳統RPC,但更通用、直接面向瀏覽器、取得更大的影響力才是谷歌推出 gRPC的初心。目前用Go開發的分散式系統,比較著名的如Kuberntes、Istio等,都是以gRPC作為分散式通訊的介面協議的。