RPC是協議:既然是協議就只是一套規範,那麼就須要有人遵循這套規範來進行實現。目前典型的RPC實現包括:Dubbo、Thrift、GRPC、Hetty等。這裏要說明一下,目前技術的發展趨勢來看,實現了RPC協議的應用工具每每都會附加其餘重要功能,例如Dubbo還包括了服務管理、訪問權限管理等功能。git
網絡協議和網絡IO模型對其透明:既然RPC的客戶端認爲本身是在調用本地對象。那麼傳輸層使用的是TCP/UDP仍是HTTP協議,又或者是一些其餘的網絡協議它就不須要關心了。既然網絡協議對其透明,那麼調用過程當中,使用的是哪種網絡IO模型調用者也不須要關心。github
信息格式對其透明:咱們知道在本地應用程序中,對於某個對象的調用須要傳遞一些參數,而且會返回一個調用結果。至於被調用的對象內部是如何使用這些參數,並計算出處理結果的,調用方是不須要關心的。那麼對於遠程調用來講,這些參數會以某種信息格式傳遞給網絡上的另一臺計算機,這個信息格式是怎樣構成的,調用方是不須要關心的。apache
應該有跨語言能力:爲何這樣說呢?由於調用方實際上也不清楚遠程服務器的應用程序是使用什麼語言運行的。那麼對於調用方來講,不管服務器方使用的是什麼語言,本次調用都應該成功,而且返回值也應該按照調用方程序語言所能理解的形式進行描述。編程
那麼上面的描述狀況能夠用下圖表示:服務器
固然,上圖是做爲RPC的調用者所觀察到的現象(而實際狀況是客戶端或多或少的仍是須要知道一些調用RPC的細節)。可是咱們是要講解RPC的基本概念,因此RPC協議內部是怎麼回事就要說清楚:網絡
Client:RPC協議的調用方。就像上文所描述的那樣,最理想的狀況是RPC Client在徹底不知道有RPC框架存在的狀況下發起對遠程服務的調用。但實際狀況來講Client或多或少的都須要指定RPC框架的一些細節。併發
Server:在RPC規範中,這個Server並非提供RPC服務器IP、端口監聽的模塊。而是遠程服務方法的具體實現(在JAVA中就是RPC服務接口的具體實現)。其中的代碼是最普通的和業務相關的代碼,甚至其接口實現類自己都不知道將被某一個RPC遠程客戶端調用。框架
Stub/Proxy:RPC代理存在於客戶端,由於要實現客戶端對RPC框架「透明」調用,那麼客戶端不可能自行去管理消息格式、不可能本身去管理網絡傳輸協議,也不可能本身去判斷調用過程是否有異常。這一切工做在客戶端都是交給RPC框架中的「代理」層來處理的。編程語言
Message Protocol:在上文咱們已經說到,一次完整的client-server的交互確定是攜帶某種兩端都能識別的,共同約定的消息格式。RPC的消息管理層專門對網絡傳輸所承載的消息信息進行編號和解碼操做。目前流行的技術趨勢是不一樣的RPC實現,爲了增強自身框架的效率都有一套(或者幾套)私有的消息格式。例如前文所講到的RMI框架使用的消息協議爲JRMP;後文咱們將詳細講解的RPC框架Thrift也有私有的消息協議,「- Transfer/Network Protocol」(固然它還支持一些通用的消息格式,如JSON)。分佈式
Transfer/Network Protocol:傳輸協議層負責管理RPC框架所使用的網絡協議、網絡IO模型。例如Hessian的傳輸協議基於HTTP(應用層協議);而Thrift的傳輸協議基於TCP(傳輸層協議)。傳輸層還須要統一RPC客戶端和RPC服務端所使用的IO模型;Selector/Processor:存在於RPC服務端,因爲服務器端某一個RPC接口的實現的特性(它並不知道本身是一個將要被RPC提供給第三方系統調用的服務)。因此在RPC框架中應該有一種「負責執行RPC接口實現」的角色。它負責了包括:管理RPC接口的註冊、判斷客戶端的請求權限、控制接口實現類的執行在內的各類工做。
IDL:實際上IDL(接口定義語言)並非RPC實現中所必須的。可是須要跨語言的RPC框架必定會有IDL部分的存在。這是由於要找到一個各類語言可以理解的消息結構、接口定義的描述形式。若是您的RPC實現沒有考慮跨語言性,那麼IDL部分就不須要包括,例如JAVA RMI由於就是爲了在JAVA語言間進行使用,因此JAVA RMI就沒有相應的IDL。
必定要說明一點,不一樣的RPC框架實現都有必定設計差別。例如生成Stub的方式不同,IDL描述語言不同、服務註冊的管理方式不同、運行服務實現的方式不同、採用的消息格式封裝不同、採用的網絡協議不同。可是基本的思路都是同樣的,上圖中的所列出的要素也都是具備的。
JAVA RMI:是否是以爲前文中咱們介紹RMI所提到幾個關鍵概念在RPC中都找獲得一些影子。是的,RPC最先就是由SUN提出,並在後來由IETF ONC修訂。RMI就是一個典型的RPC實現,只不過RMI不支持跨語言性,因此RMI中也沒有IDL存在的必要。可是RMI真心快,而且因爲沒有IDL的存在,在構建一套完整的RPC實現時要比其餘RPC框架少了一些步驟,因此使用起來也比較簡單。若是您的業務需求中並不存在跨語言的考慮,而且基本上主要系統都是用JAVA實現,那麼RMI絕對是您一個能夠考慮的方案。
GRPC:GRPC是一個高性能、通用的開源RPC框架,由Google主要面向移動應用開發並基於HTTP/2協議(注意是HTTP/2協議,不是咱們常使用的HTTP 1_1。HTTP/2協議詳細的介紹能夠參見官方地址:https://http2.github.io/)標準而設計,基於ProtoBuf(Protocol Buffers)序列化協議開發,且支持衆多開發語言。爲了支持GRPC的跨語言性,GRPC有一套獨立存在IDL語言。不過因爲GRPC是Google的開源產品,在信息格式封裝方面Google主要仍是推廣的本身的ProtoBuf,因此GPRC是不支持其餘信息格式的(至少ProtoBuf效率是你們有目共睹的)。關於GRPC詳細的使用介紹,能夠參見官方地址:https://github.com/grpc/grpc
Thrift:Thrift是Facebook的一個開源項目,後來進入Apache進行孵化。Thrift也是支持跨語言的,因此它有本身的一套IDL。目前它支持幾乎全部主流的編程語言:C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi and other languages。Thrift能夠支持多種信息格式,除了Thrift私有的二進制編碼規則和一種LVQ(相似於TLV消息格式)的消息格式,還有常規的JSON格式。Thrift的網絡協議創建在TCP協議基礎上,而且支持阻塞式IO模型和多路IO複用模型。咱們將在後文詳細講解Apache Thrift的使用。Thrift也是目前最流行的RPC框架之一,從網絡上各類性能測試狀況開,Thrift的性能都是領先的。Thrift的官網地址爲:http://thrift.apache.org/
Hetty:Hetty是一款構建於Netty和Hessian基礎上的高性能的RPC框架。Hetty的網絡協議基於HTTP,因爲採用了Netty,因此Hetty支持阻塞式IO模型和多路IO複用模型。Hetty的消息格式採用私有的二進制流格式。
Dubbo:Dubbo是Alibaba開源的分佈式服務框架。注意我說的是分佈式服務框架,不是RPC框架(用比較嚴謹的詞語歸納,應該是「服務治理框架」)。除了集成RPC的規範外,Dubbo還在RPC的上層搭建服務層功能、配置層功能、服務路由功能(加上真正的RPC規範實現總共有10層)。在後文講解「服務治理」時會重點講解Dubbo的原理和使用。
其餘的RPC框架:除了上訴的RPC協議的實現外,還有:Wildfly、Hprose等等。Hprose是一款國人主導的RPC實現,感興趣的讀者能夠去看看(http://www.hprose.com/)。另外基於RPC的定義,Xfire,CXF這些Web Service框架也屬於RPC:WSDL描述文件就是他們的IDL,經過WSDL爲不一樣的編程語言生成Stub、經過不一樣的Web服務器管理具體服務實現的運行過程、HTTP是它們的通訊協議、XML是它們的消息格式。
在物理服務器性能相同的狀況下,如下幾個因素會對一款RPC框架的性能產生直接影響:
所支持的網絡IO模型:您的RPC服務器能夠只支持傳統的阻塞式同步IO,也能夠作一些改進讓您的RPC服務器支持非阻塞式同步IO,或者在您的服務器上實現對多路IO模型的支持。這樣的RPC服務器的性能在高併發狀態下,會有很大的差異。特別是單位處理性能下對內存、CPU資源的使用率。
基於的網絡協議:通常來講您能夠選擇讓您的RPC使用應用層協議,例如HTTP或者以前咱們提到的HTTP/2協議,或者使用TCP協議,讓您的RPC框架工做在傳輸層。工做在哪一層網絡上會對RPC框架的工做性能產生必定的影響,可是對RPC最終的性能影響並不大。可是至少從各類主流的RPC實現來看,沒有采用UDP協議作爲主要的傳輸協議的。
選擇的消息封裝格式:選擇或者定義一種消息格式的封裝,要考慮的問題包括:消息的易讀性、描述單位內容時的消息體大小、編碼難度、解碼難度、解決半包/粘包問題的難易度。固然若是您只是想定義一種RPC專用的消息格式,那麼消息的易讀性可能不是最須要考慮的。消息封裝格式的設計是目前各類RPC框架性能差別的最重要緣由,這就是爲何幾乎全部主流的RPC框架都會設計私有的消息封裝格式的緣由。
實現的服務處理管理方式:在高併發請求下,如何管理註冊的服務也是一個性能影響點。您可讓RPC的Selector/Processor使用單個線程運行服務的具體實現(這意味着上一個客戶端的請求沒有處理完,下一個客戶端的請求就須要等待)、您也能夠爲每個RPC具體服務的實現開啓一個獨立的線程運行(能夠一次處理多個請求,可是操做系統對於「可運行的最大線程數」是有限制的)、您也能夠線程池來運行RPC具體的服務實現(目前看來,在單個服務節點的狀況下,這種方式是比較好的)、您還能夠經過註冊代理的方式讓多個服務節點來運行具體的RPC服務實現。