文章目錄
1 Dubbo原理
1.1 Dubbo架構圖
1.1.1 Dubbo基本理解
Dubbo
:一個分佈式、高性能、透明化的RPC
服務框架
作用:提供服務自動註冊、自動發現等高效服務治理方案
1.1.2 架構內角色說明:
Provider
:提供者,服務發佈方.Consumer
:消費者, 調用服務方Container
:Dubbo
容器.依賴於Spring
容器.Registry
: 註冊中心.當Container
啓動時把所有可以提供的服務列表上Registry
中進行註冊,作用:告訴Consumer
提供了什麼服務和服務方在哪裏Monitor
:監聽器虛線
都是異步訪問,實線
都是同步訪問藍色虛線
:在啓動時完成的功能紅色虛線(實線)
都是程序運行過程中執行的功能- 所有的角色都是可以在單獨的服務器上,所以必須遵守特定的協議
- 上圖中,除了監視器
monitor
這裏是短連接,其他連接都是長連接
1.1.3 運行原理
啓動容器
,相當於在啓動Dubbo
的Provider
- 啓動後會去註冊中心進行註冊,註冊所有可以提供的服務列表
- 在
Consumer
啓動後會去Registry
中獲取服務列表和Provider
的地址,進行訂閱. - 當
Provider
有修改後,註冊中心會把消息推送給Consummer
,使用了觀察者設計模式(又叫發佈/訂閱設計模式) - 根據獲取到的
Provider
地址,真實調用Provider
中功能,在Consumer
方使用了代理設計模式,創建一個Provider
方類的一個代理對象,通過代理對象獲取Provider
中真實功能,起到保護Provider
真實功能的作用. Consumer
和Provider
每隔1
分鐘向Monitor
發送統計信息,統計信息包含,訪問次數,頻率等
1.2 底層調用真正原理
Dubbo
底層採用Socket
進行通信,Dubbo
缺省協議採用單一長連接和NIO
異步通訊,適合於小數據量大併發的服務調用,以及服務消費者機器數遠大於服務提供者機器數的情況
長連接
:一組信息(通常是字節)的各位數據被同時傳送的通信方法稱爲並行通信
。並行通信依靠並行I/O
接口實現。並行通信速度快,但傳輸線根數多,只適用於近距離
(相距數公尺)的通信。短連接
:一組信息的各位數據被逐位順序傳送的通信方式稱爲串行通信
。串行通信可通過串行接口來實現。串行通信速度慢
,但傳輸線少,適宜長距離
通信。
1.2.1 底層調用原理
分析源代碼,基本原理如下:
client
一個線程調用遠程接口,生成一個唯一的ID
(比如一段隨機字符串,UUID
等),Dubbo
是使用AtomicLong
從0
開始累計數字的- 將打包的方法調用信息(如調用的接口名稱,方法名稱,參數值列表等),和處理結果的回調對象
callback
,全部封裝在一起,組成一個對象object
- 向專門存放調用信息的全局
ConcurrentHashMap
裏面put(ID, object)
- 將
ID
和打包的方法調用信息封裝成一對象connRequest
,使用IoSession.write(connRequest)
異步發送出去 - 當前線程再使用
callback的get()
方法試圖獲取遠程返回的結果,在get()
內部,則使用synchronized
獲取回調對象callback
的鎖, 再先檢測是否已經獲取到結果,如果沒有,然後調用callback的wait()
方法,釋放callback
上的鎖,讓當前線程處於等待狀態 - 服務端接收到請求並處理後,將結果(此結果中包含了前面的
ID
,即回傳)發送給客戶端,客戶端socket
連接上專門監聽消息的線程收到消息,分析結果,取到ID
,再從前面的ConcurrentHashMap
裏面get(ID)
,從而找到callback
,將方法調用結果設置到callback
對象裏。 - 監聽線程接着使用
synchronized
獲取回調對象callback
的鎖(因爲前面調用過wait()
,那個線程已釋放callback
的鎖了),再notifyAll()
,喚醒前面處於等待狀態的線程繼續執行(callback
的get()
方法繼續執行就能拿到調用結果了),至此,整個過程結束
1.2.2 調用相關問題
當前線程怎麼讓它暫停
,等結果回來後,再向後執行?
答:先生成一個對象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
,讓其唯一,然後傳遞給服務端,再服務端又回傳回來,這樣就知道結果是原先哪個線程的了。
1.2.3 調用協議說明
Dubbo
缺省協議,使用基於mina1.1.7+hessian3.2.1
的tbremoting
交互。
- 連接個數:單連接
- 連接方式:長連接
- 傳輸協議:
TCP
- 傳輸方式:
NIO
異步傳輸 - 序列化:
Hessian
二進制序列化 - 適用範圍:傳入傳出參數數據包較小(建議小於
100K
),消費者比提供者個數多,單一消費者無法壓滿提供者,儘量不要用dubbo
協議傳輸大文件或超大字符串。 - 適用場景:常規遠程服務方法調用
通常,一個典型的同步遠程調用應該是這樣的:
1.2.4 provider和consumer的invoke
下面我們用一個精簡的圖來說明最重要的兩種Invoker
:服務提供Invoker
和服務消費Invoker
:
1.2.5 服務暴露和消費的詳細過程
1.2.5.1 provider暴露服務的詳細過程
服務提供者暴露服務的主過程:
首先ServiceConfig
類拿到對外提供服務的實際類ref
(如:HelloWorldImpl
),然後通過ProxyFactory
類的getInvoker
方法使用ref
生成一個AbstractProxyInvoker
實例,到這一步就完成具體服務到Invoker
的轉化。
接下來就是Invoker
轉換到Exporter
的過程。
Dubbo
處理服務暴露的關鍵就在Invoker
轉換到Exporter
的過程(如上圖中的紅色部分),下面我們以Dubbo
和RMI
這兩種典型協議的實現來進行說明:
Dubbo
的實現:
Dubbo
協議的Invoker
轉爲Exporter
發生在DubboProtocol
類的export
方法,它主要是打開socket
偵聽服務,並接收客戶端發來的各種請求,通訊細節由Dubbo
自己實現。RMI
的實現:
RMI
,(Remote Method Invoke
遠程方法調用,只支持java
)協議的Invoker
轉爲Exporter
發生在RmiProtocol
類的export
方法,
它通過Spring
或Dubbo
或JDK
來實現RMI
服務,通訊細節這一塊由JDK
底層來實現,這就省了不少工作量。
1.2.5.2 consumer消費服務過程
服務消費的主過程:
首先ReferenceConfig
類的init
方法調用Protocol
的refer
方法生成Invoker
實例(如上圖中的紅色部分),這是服務消費的關鍵。
接下來,然後通過ProxyFactory
類的getProxy
方法把Invoker
轉換爲客戶端需要的接口(如:HelloWorld
)
2 Dubbo分層
Dubbo
大致上分爲三層,分別是:
- 業務層
RPC
層Remoting
層