連接:https://www.jianshu.com/p/362880b635f0html
在傳統的開發模式中,咱們一般將系統的各個服務部署在單臺機器,隨着服務的擴展,這種方式已經徹底沒法知足系統大規模的擴展須要,分佈式系統由此誕生,在分佈式系統中,最重要就是各個服務之間的 RPC 調用。java
RPC 全稱 Remote Procedure Call——遠程過程調用,它是一種經過網絡從遠程計算機程序上請求服務,而不須要了解底層網絡技術的方式。簡單一點就是:經過必定協議和方法使得調用遠程計算機上的服務,就像調用本地服務同樣。json
一般來講,RPC 的實現方式有不少,能夠基於常見的 HTTP 協議,也能夠在TCP上層封裝自定義協議,常見的 Web Service 就是基於 HTTP 協議的 RPC,HTTP 協議的優勢是具備良好的跨平臺性,特別適合異構系統較多的公司,可是因爲 HTTP 報頭較爲冗長,性能較差,基於 TCP 協議的 RPC 能夠創建長鏈接,速度和效率明顯,可是難度和複雜程度很高。後端
RPC 的誕生讓構建分佈式應用更容易,極大的擴大系統的可擴展性,容錯性。爲複雜業務邏輯的系統進行服務化改造和高可用性升級提供了可能。服務器
RPC採用客戶機/服務器模式。請求程序就是一個客戶機,而服務提供程序就是一個服務器。首先,客戶機調用進程發送一個有進程參數的調用信息到服務進程,而後等待應答信息。在服務器端,進程保持睡眠狀態直到調用信息的到達爲止。當一個調用信息到達,服務器得到進程參數,計算結果,發送答覆信息,而後等待下一個調用信息,最後,客戶端調用進程接收答覆信息,得到進程結果,而後調用執行繼續進行。restful
目前,有多種 RPC 模式和執行。最初由 Sun 公司提出。IETF ONC 憲章從新修訂了 Sun 版本,使得 ONC RPC 協議成爲 IETF 標準協議。如今使用最廣泛的模式和執行是開放式軟件基礎的分佈式計算環境(DCE)網絡
協議結構架構
遠程過程調用(RPC)信息協議由兩個不一樣結構組成:調用信息和答覆信息。信息流程以下所示:併發
RPC:遠程過程調用流程負載均衡
RPC 調用信息:每條遠程過程調用信息包括如下無符號整數字段,以獨立識別遠程過程:
程序號(Program number)
程序版本號(Program version number)
過程號(Procedure number)
RPC 調用信息主體形式以下:
struct call_body {
unsigned int rpcvers;
unsigned int prog;
unsigned int vers;
unsigned int proc;
opaque_auth cred;
opaque_auth verf;
1 parameter
2 parameter . . . };
RPC 答覆信息:RPC 協議的答覆信息的改變取決於網絡服務器對調用信息是接收仍是拒絕。答覆信息請求包括區別如下情形的各類信息:
RPC 成功執行調用信息。.
RPC 的遠程實現不是協議第二版,返回 RPC 支持的最低和最高版本號。
在遠程系統中,遠程程序不可用。
遠程程序不支持被請求的版本號。返回遠程程序所支持的最低和最高版本號。
請求的過程號不存在。一般是呼叫方協議或程序差錯。
RPC答覆信息形式以下:
enum reply_stat stat
{MSG_ACCEPTED = 0,
MSG_DENIED = 1 };
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.服務端(Server):RPC 服務的提供者,負責將 RPC 服務導出;
2.客戶端 (Client):RPC 服務的消費者,負責調用 RPC 服務;
3.代理(Proxy):經過動態代理,提供對遠程接口的代理實現;
4.執行器(Invoker):對於客戶端:主要負責服務調用的編碼,調用請求發送和等待結果返回;對於服務方:負責處理調用邏輯並返回調用結果;
5.協議管理(Protocol):協議管理組件,負責整個 RPC 通訊協議的編/解碼;
6.鏈接端口(Connector):負責維持客戶方和服務方的長鏈接通道;
7.後臺處理(Processor):負責整個調用服務中的管理調度,包括線程池,分發,異常處理等;
8.鏈接通道(Channel):客戶端和服務器端的數據傳輸通道。
具體到 JAVA 平臺來講,其中的3,4一般使用動態代理實現,5,6,7,8使用 NIO 或者一些高性能 NIO 框架,如 mina,netty 實現。
在進一步拆解了組件並劃分了職責以後,這裏以一個最簡單 Java RPC 框架實現爲例,對 RPC 具體邏輯進行分析。
RPC 框架服務發佈代碼:
服務端發佈服務的代碼如上,首先校驗傳入的端口和服務是否合法,而後開啓一個 socket 監聽,這兒爲了簡便,沒有采用 NIO 方式,同時直接採用 java 的序列化方式,將傳入的數據經過反射取出調用的方法和參數,本地執行後將運行結果經過 socket 套接字返回給客戶端。
RPC 框架服務調用代碼
框架中客戶端調用的代碼中,首先校驗對應的端口和主機是否合法,而後經過動態代理生成一個代理對象,在代理對象的方法中,攔截調用,經過創建 socket 鏈接,將方法和參數傳遞到遠端執行並獲取遠程執行返回結果。
RPC 調用測試:
如上圖所示,服務器端發佈一個接口服務 HelloService,客戶端成功經過 RPC 調用。
思 考 篇
在上面的示例程序當中,咱們僅僅是完成了一個基本的遠程調用,並無實現 RPC 框架中的不少組件功能,從最簡單的代碼版本中咱們能夠發現,發起一個 RPC 調用,須要傳輸的最基本數據以下:
接口方法:包括接口的名字和相應的方法名字;
方法參數:包括參數的類型和取值;
附件參數,包括調用接口版本,接口超時時間等等。
所以,若是要自定義協議實現 RPC,咱們必須再協議的消息體中包含這部分數據,另外,咱們須要定義一些協議元數據,這些元數據一般放在協議頭中,和包含必要參數的協議體一期組成了自定義消息。
元數據一般會包含如下字段,大部分字段只須要1-2位:
magic: 魔數,方便協議解碼
header_size: 協議頭大小,便於解碼,同時可用用於處理TCP粘包問題
id :消息 id,用來標示此次調用
version: 接口版本
type:消息類型,可用包括普通調用消息,心跳,控制消息
status:消息狀態,是否首次處理或者已經處理
body_size: 消息體長度
serialize_type:消息體序列化類型
body:具體消息
消息內容在網絡上傳輸須要對其進行編碼,這個編碼的過程就是序列化過程,顯然,對於網絡傳輸的數據,在可以保證信息足夠解碼的狀況下,序列化的大小越小,傳輸的開銷就越小,效率就越高,目前 JAVA 平臺經常使用的序列化方式有:xml,json ,binary(包括 thrift; hession; kryo 等)。
在 RPC 調用中咱們推薦使用二進制方式進行序列化,在大部分的測試中,二進制方式序列化具備至關好的表現,另一個比較有意思的地方是,每一次 JDK 版本的升級,JAVA 自帶的序列化方式的效率都有提高。
從前面的示例代碼中,咱們僅僅簡單的考慮了實現了組件中的服務端和客戶端,並無考慮效率問題,在一個完整的 RPC 框架中,咱們須要考慮實現並優化調用的每個地方,同時,爲了符合業務需求,須要有很高的可靠性和容錯機制。
具體來講,在動態代理模塊,咱們不會採用 java 自帶的動態接口,而是會採用一些性能更高的三方庫,在鏈接通道和鏈接模塊,咱們會採用更優秀的三方NIO,如 netty 來實現,在後端處理模塊,咱們也不會僅僅是執行結果並返回,要考慮更多的東西:
併發控制:當多個請求併發處理的時候,如何管理和控制線程池和超時等待時間;
版本隔離:當服務有多個版本的時候,如何讓不一樣的調用者可以調用正確的服務;
服務路由:當服務提供者有多臺機器的時候,如何提升系統負載均衡,路由到正確的服務端;
服務降級:當多個服務重要性有不一樣的時候,若是保證核心業務的穩定性,適當的下降非核心業務優先級;
服務監控和報警:服務出現異常狀況時候,運維和對應的系統負責人可以第一時間獲得告警和錯誤信息。
以上的思考大部分要結合運維層面一塊兒考慮,可是 RPC 框架自己也要提供足夠的支持才能保證它足夠的健壯性。
雖然 RPC 有足夠多的優勢讓你去使用,可是當真正轉向服務化的時候,依然有不少須要考慮的地方:
網絡問題:本地調用無需考慮是否可以執行問題,網絡調用可能會由於各類外部網絡環境,端口攔截,IP 受限等可能狀況致使沒法成功執行。因此 RPC 的服務端一般要考慮冪等性和容錯性,接口須要較強的魯棒性設計。
異常處理:RPC 和本地服務最大的不一樣就是 RPC 服務存在分佈式一致性問題,當服務沒有調用成功狀況下,本地和遠程的服務可能處於一個不一致的狀態,如何進行異常處理和事物的回滾機制也是一個須要考慮的問題,是須要保障強一致性和最終一致性一般取決於具體的業務需求。
因爲網絡緣由,RPC 服務一般會被本地服務處理慢一個數量級,在比較輕量級的業務和併發量很小的狀況下,並不須要 RPC 服務,引入 RPC 服務後,不管是系統的調試,仍是線上問題分析都會變得很是複雜,是否引入也須要權衡相關利弊。
本文簡單的介紹了 RPC 的基本知識和相關分析以供拋磚引玉,進一步的學習能夠參考當前最主流的一些 RPC 框架,如dubbo, protobuff ,thrift 經過對其源碼的深刻學習,相信能獲益匪淺。