Dubbo系列(三)dubbo的核心技術--RPC調用

dubbo的核心技術--RPC調用:分爲倆部分RPC協議Protocol和方法調用Invoke;網絡

1、RPC協議Protocol(Remote Procedure Call)遠程過程調用協議併發

一、咱們平時使用最多的http協議其實也屬於RPC協議,下圖分別是普通的傳輸層TCP和應用層http與dubbo優化後的TCP和dubbo協議進行對比。框架

總結:異步

原生的傳輸層協議(TCP)須要網絡三次握手和四次揮手,客戶端與服務端的創建連接成本太高,dubbo對TCP進行優化,實現單一長鏈接,下降網絡連接成本;socket

原生的應用層協議(http)通用性高、拓展性強,同時協議複雜解析成本高,dubbo消費者調用服務者時只須要傳輸不多字節,而且結構簡單,便與解析;測試

 二、dubbo遠程同步調用原理分析:大數據

Dubbo默認協議採用單一長鏈接和NIO異步通信,適合於小數據量大併發的服務調用,以及服務消費者機器數遠大於服務提供者機器數的狀況。Dubbo缺省協議不適合傳送大數據量的服務,好比傳文件,傳視頻等,除非請求量很低。優化

dubbo缺省協議,使用基於mina1.1.7+hessian3.2.1的tbremoting交互。spa

  • 鏈接個數:單鏈接
  • 鏈接方式:長鏈接
  • 傳輸協議:TCP
  • 傳輸方式:NIO異步傳輸
  • 序列化:Hessian二進制序列化
  • 適用範圍:傳入傳出參數數據包較小(建議小於100K),消費者比提供者個數多,單一消費者沒法壓滿提供者,儘可能不要用dubbo協議傳輸大文件或超大字符串
  • 適用場景:常規遠程服務方法調用

爲何採用異步單一長鏈接:由於服務的現狀大都是服務提供者少,而服務的消費者多,經過單一長鏈接,保證單一消費者不會壓死提供者,長鏈接減小鏈接握手驗證,並使用異步IO,複用線程池,下降網絡連接成本。線程

爲何要消費者比提供者個數多:由於dubbo協議採用單一長鏈接,假設網絡爲千兆網卡(1024Mbit=128MByte),根據測試經驗數據每條鏈接最多隻能壓滿7MByte(不一樣的網絡環境可能不同,僅供參考),理論上1個服務提供者須要20個服務消費者才能壓滿網卡。

爲何不能傳大包:若是每次請求的數據包大小爲500KByte,則單個服務提供者的TPS(每秒處理事務數)最大爲:128Mbyte/500KByte=262. 單個消費者調用單個服務提供者的TPS(每秒處理事務數)最大爲: 7MByte/500KByte = 14。可看出網絡將成爲瓶頸。

一般,一個典型的同步遠程調用應該是這樣的:

一、客戶端線程調用遠程接口,向服務端發送請求,同時當前線程應該處於「暫停「狀態,即線程不能向後執行了,必須要拿到服務端給本身的結果後才能向後執行;
二、服務端接到客戶端請求後,處理請求,將結果給客戶端;
三、客戶端收到結果,而後當前線程繼續日後執行;

Dubbo底層使用Socket發送消息的形式進行數據傳遞,結合了mina框架,使用IoSession.write()方法,這個方法調用後對於整個遠程調用(從發出請求到接收到結果)來講是一個異步的,即對於當前線程來講,將請求發送出來,線程就能夠日後執行了,至於服務端的結果,是服務端處理完成後,再以消息的形式發送給客戶端的。----紅字部分我沒有搞明白!!

因而這裏出現了2個問題:
  • 當前線程怎麼讓它「暫停」,等結果回來後,再向後執行?
  • 正如前面所說,Socket通訊是一個全雙工的方式,若是有多個線程同時進行遠程方法調用,這時創建在client server之間的socket鏈接上會有不少雙方發送的消息傳遞,先後順序也多是亂七八糟的,server處理完結果後,將結果消息發送給client,client收到不少消息,怎麼知道哪一個消息結果是原先哪一個線程調用的?
基本原理以下:
1.client一個線程調用遠程接口,生成一個惟一的ID(好比一段隨機字符串,UUID等),Dubbo是使用AtomicLong從0開始累計數字的。
2.將打包的方法調用信息(如調用的接口名稱,方法名稱,參數值列表等),和處理結果的回調對象callback,所有封裝在一塊兒,組成一個對象object。
3.向專門存放調用信息的全局ConcurrentHashMap中put(Id,Object)。
4.將ID和打包的方法調用信息封裝成以對象connRequest,使用IoSession.write(connRequest)異步發送出去。
5.當前線程再使用callback的get()方法視圖獲取遠程方法返回的結果,在get()內部,則使用synchronized獲取回調對象callback的鎖,再先檢測是否已經獲取到結果,若是沒有,就調用callback的wait()方法,釋放callback上的鎖,讓當前線程處於等待狀態。
6.服務端接收到請求並處理後,將結果(此結果中包含了簽名的ID,即回傳)發送給客戶端,客戶端socket鏈接上專門監聽消息的線程收到消息,分析結果,取到ID,再從前面的ConcurrentHashMap中get(Id),從而找到callback,將方法調用結果設置到callback對象裏。
7.監聽線程接着使用synchronized獲取回調對象callback的鎖(由於前面調用過wait(),那個線程已經釋放callback的鎖了),再notifyAll(),喚醒前面處於等待狀態的線程繼續執行(callback的get()方法繼續執行就能拿到調用結果了),至此,整個過程結束。
須要注意的是,這裏的callback對象是每次調用產生一個新的,不能共享,不然會有問題;另外ID必需至少保證在一個Socket鏈接裏面是惟一的。
 
如今,前面兩個問題已經有答案了:
  • 當前線程怎麼讓它「暫停」,等結果回來後,再向後執行?
     答:先生成一個對象obj,在一個全局map裏put(ID,obj)存放起來,再用synchronized獲取obj鎖,再調用obj.wait()讓當前線程處於等待狀態,而後另外一消息監聽線程等到服務端結果來了後,再map.get(ID)找到obj,再用synchronized獲取obj鎖,再調用obj.notifyAll()喚醒前面處於等待狀態的線程。
  • 正如前面所說,Socket通訊是一個全雙工的方式,若是有多個線程同時進行遠程方法調用,這時創建在client server之間的socket鏈接上會有不少雙方發送的消息傳遞,先後順序也多是亂七八糟的,server處理完結果後,將結果消息發送給client,client收到不少消息,怎麼知道哪一個消息結果是原先哪一個線程調用的?
     答:使用一個ID,讓其惟一,而後傳遞給服務端,再服務端又回傳回來,這樣就知道結果是原先哪一個線程的了。
相關文章
相關標籤/搜索