通俗地解釋一下RPC框架

 

什麼是 RPC ?

RPC (Remote Procedure Call)即遠程過程調用,是分佈式系統常見的一種通訊方法,已經有 40 多年曆史。當兩個物理分離的子系統須要創建邏輯上的關聯時,RPC 是牽線搭橋的常見技術手段之一。除 RPC 以外,常見的多系統數據交互方案還有分佈式消息隊列、HTTP 請求調用、數據庫和分佈式緩存等。nginx

 

 

其中 RPC 和 HTTP 調用是沒有通過中間件的,它們是端到端系統的直接數據交互。HTTP 調用其實也能夠當作是一種特殊的 RPC,只不過傳統意義上的 RPC 是指長鏈接數據交互,而 HTTP 通常是指即用即走的短連接。git

RPC 在咱們熟知的各類中間件中都有它的身影。Nginx/Redis/MySQL/Dubbo/Hadoop/Spark/Tensorflow 等重量級開源產品都是在 RPC 技術的基礎上構建出來的,咱們這裏說的 RPC 指的是廣義的 RPC,也就是分佈式系統的通訊技術。RPC 在技術中的地位比如咱們身邊的空氣,它無處不在,可是又有不少人根本不知道它的存在。github

 

本地過程調用數據庫

RPC就是要像調用本地的函數同樣去調遠程函數。在研究RPC前,咱們先看看本地調用是怎麼調的。假設咱們要調用函數Multiply來計算lvalue * rvalue的結果:緩存

1 int Multiply(int l, int r) {
2    int y = l * r;
3    return y;
4 }
5 
6 int lvalue = 10;
7 int rvalue = 20;
8 int l_times_r = Multiply(lvalue, rvalue);

那麼在第8行時,咱們實際上執行了如下操做:服務器

  1. 將 lvalue 和 rvalue 的值壓棧
  2. 進入Multiply函數,取出棧中的值10 和 20,將其賦予 l 和 r
  3. 執行第2行代碼,計算 l * r ,並將結果存在 y
  4. 將 y 的值壓棧,而後從Multiply返回
  5. 第8行,從棧中取出返回值 200 ,並賦值給 l_times_r

以上5步就是執行本地調用的過程。(20190116注:以上步驟只是爲了說明原理。事實上編譯器常常會作優化,對於參數和返回值少的狀況會直接將其存放在寄存器,而不須要壓棧彈棧的過程,甚至都不須要調用call,而直接作inline操做。僅就原理來講,這5步是沒有問題的。)網絡

 

遠程過程調用帶來的新問題框架

在遠程調用時,咱們須要執行的函數體是在遠程的機器上的,也就是說,Multiply是在另外一個進程中執行的。這就帶來了幾個新問題:socket

  1. Call ID映射。咱們怎麼告訴遠程機器咱們要調用Multiply,而不是Add或者FooBar呢?在本地調用中,函數體是直接經過函數指針來指定的,咱們調用Multiply,編譯器就自動幫咱們調用它相應的函數指針。可是在遠程調用中,函數指針是不行的,由於兩個進程的地址空間是徹底不同的。因此,在RPC中,全部的函數都必須有本身的一個ID。這個ID在全部進程中都是惟一肯定的。客戶端在作遠程過程調用時,必須附上這個ID。而後咱們還須要在客戶端和服務端分別維護一個 {函數 <--> Call ID} 的對應表。二者的表不必定須要徹底相同,但相同的函數對應的Call ID必須相同。當客戶端須要進行遠程調用時,它就查一下這個表,找出相應的Call ID,而後把它傳給服務端,服務端也經過查表,來肯定客戶端須要調用的函數,而後執行相應函數的代碼。
  2. 序列化和反序列化。客戶端怎麼把參數值傳給遠程的函數呢?在本地調用中,咱們只須要把參數壓到棧裏,而後讓函數本身去棧裏讀就行。可是在遠程過程調用時,客戶端跟服務端是不一樣的進程,不能經過內存來傳遞參數。甚至有時候客戶端和服務端使用的都不是同一種語言(好比服務端用C++,客戶端用Java或者Python)。這時候就須要客戶端把參數先轉成一個字節流,傳給服務端後,再把字節流轉成本身能讀取的格式。這個過程叫序列化和反序列化。同理,從服務端返回的值也須要序列化反序列化的過程。
  3. 網絡傳輸。遠程調用每每用在網絡上,客戶端和服務端是經過網絡鏈接的。全部的數據都須要經過網絡傳輸,所以就須要有一個網絡傳輸層。網絡傳輸層須要把Call ID和序列化後的參數字節流傳給服務端,而後再把序列化後的調用結果傳回客戶端。只要能完成這二者的,均可以做爲傳輸層使用。所以,它所使用的協議實際上是不限的,能完成傳輸就行。儘管大部分RPC框架都使用TCP協議,但其實UDP也能夠,而gRPC乾脆就用了HTTP2。Java的Netty也屬於這層的東西。

因此,要實現一個RPC框架,其實只須要把以上三點實現了就基本完成了。分佈式

Call ID映射能夠直接使用函數字符串,也可使用整數ID。映射表通常就是一個哈希表。

序列化反序列化能夠本身寫,也可使用Protobuf或者FlatBuffers之類的。

網絡傳輸庫能夠本身寫socket,或者用asio,ZeroMQ,Netty之類。

 

最後,有興趣的能夠看咱們本身寫的一個小而精的RPC庫 tinyrpc(hjk41/tinyrpc),對於理解RPC如何工做頗有好處。

 

---------------------------------------------------------------------------------------------------------------

SOA的發展
 

咱們在作一個訪問量不大的項目的時候,一臺服務器部署上一個應用+數據庫也就夠了.

那麼訪問量稍微大一點以後呢,爲了解決用戶反饋的卡,反應慢的狀況,咱們就上集羣.架設nginx,部署多個服務,由nginx負責把請求轉發到其餘服務上,這樣就解決了用戶說的卡慢問題.

過了一段時間以後呢,咱們發現數據庫已經扛不住了,應用服務無缺,數據庫有時候宕機. 那這個時候呢,咱們就上數據庫讀寫分離,再架設幾臺數據庫服務器,作主從,作分庫分表. 而後數據庫也不宕機了,服務又恢復了流暢.

又過了一段時間,公司事業增增日上,服務訪問量愈來愈高,且大部分都是查詢, 吸收以前宕機且爲了辦證數據庫的健壯性,咱們這個時候又加上了緩存, 把用戶高頻次訪問的數據放到緩存裏.

後來,項目功能愈來愈多,整個項目也愈發龐大,修改一個類就須要全盤上傳,切換nginx重啓,這樣的發佈流程愈來愈長,愈來愈繁雜.而後咱們開始把模塊拆分,用戶信息分個項目,訂單系統分一個項目.這樣就達到了,用戶模塊代碼修改的時候,只須要更新用戶信息服務就行了.可是仍是須要切換頂層的nginx.把要重啓的服務的流量切到可用服務上. 這個時候咱們就想到了RPC

那麼RPC解決了什麼呢? 全部的服務在啓動的時候註冊到一個註冊機裏面,而後頂層處理在接收到nginx的請求時,去註冊機找一個可用的服務,並調用接口. 這樣子呢,在不加新功能的時候,頂層處理服務咱們就不須要動了? 那修改了用戶信息項目的時候,咱們只須要一個個更新用戶信息項目的服務羣就行了?

這樣的話,不管是擴展仍是服務的健壯性都妥妥的了

相關文章
相關標籤/搜索