因爲Dubbo底層採用Socket進行通訊,本身對通訊理理論也不是很清楚,因此順便把通訊的知識也學習一下。html
n 通訊理論apache
計算機與外界的信息交換稱爲通訊。基本的通訊方法有並行通訊和串行通訊兩種。編程
1.一組信息(一般是字節)的各位數據被同時傳送的通訊方法稱爲並行通訊。並行通訊依靠並行I/O接口實現。並行通訊速度快,但傳輸線根數多,只適用於近距離(相距數公尺)的通訊。網絡
2.一組信息的各位數據被逐位順序傳送的通訊方式稱爲串行通訊。串行通訊可經過串行接口來實現。串行通訊速度慢,但傳輸線少,適宜長距離通訊。併發
串行通訊按信息傳送方向分爲如下3種:框架
1) 單工異步
只能一個方向傳輸數據socket
2) 半雙工ide
信息能雙向傳輸,但不能同時雙向傳輸學習
3) 全雙工
能雙向傳輸而且能夠同時雙向傳輸
n Socket
Socket 是一種應用接口, TCP/IP 是網絡傳輸協議,雖然接口相同, 可是不一樣的協議會有不一樣的服務性質。建立Socket 鏈接時,能夠指定使用的傳輸層協議,Socket 能夠支持不一樣的傳輸層協議(TCP 或UDP ),當使用TCP 協議進行鏈接時,該Socket 鏈接就是一個TCP 鏈接。Soket 跟TCP/IP 並無必然的聯繫。Socket 編程接口在設計的時候,就但願也能適應其餘的網絡協議。因此,socket 的出現只是能夠更方便的使用TCP/IP 協議棧而已。
引自:http://hi.baidu.com/lewutian/blog/item/b28e27fd446d641d09244d08.html
上一個通訊理論實際上是想說Socket(TCP)通訊是全雙工的方式
n Dubbo遠程同步調用原理分析
從Dubbo開源文檔上了解到一個調用過程以下圖
http://code.alibabatech.com/wiki/display/dubbo/User+Guide#UserGuide-APIReference
另外文檔裏有說明:Dubbo缺省協議採用單一長鏈接和NIO異步通信,適合於小數據量大併發的服務調用,以及服務消費者機器數遠大於服務提供者機器數的狀況。
Dubbo缺省協議,使用基於mina1.1.7+hessian3.2.1的tbremoting交互。
- 鏈接個數:單鏈接
- 鏈接方式:長鏈接
- 傳輸協議:TCP
- 傳輸方式:NIO異步傳輸
- 序列化:Hessian二進制序列化
- 適用範圍:傳入傳出參數數據包較小(建議小於100K),消費者比提供者個數多,單一消費者沒法壓滿提供者,儘可能不要用dubbo協議傳輸大文件或超大字符串。
- 適用場景:常規遠程服務方法調用
一般,一個典型的同步遠程調用應該是這樣的:
1, 客戶端線程調用遠程接口,向服務端發送請求,同時當前線程應該處於「暫停「狀態,即線程不能向後執行了,必須要拿到服務端給本身的結果後才能向後執行
2, 服務端接到客戶端請求後,處理請求,將結果給客戶端
3, 客戶端收到結果,而後當前線程繼續日後執行
Dubbo裏使用到了Socket(採用apache mina框架作底層調用)來創建長鏈接,發送、接收數據,底層使用apache mina框架的IoSession進行發送消息。
查看
Dubbo文檔及源代碼可知,Dubbo底層使用Socket發送消息的形式進行數據傳遞,結合了mina框架,使用IoSession.write()方法,這個方法調用後對於整個遠程調用(從發出請求到接收到結果)來講是一個異步的,即對於當前線程來講,將請求發送出來,線程就能夠日後執行了,至於服務端的結果,是服務端處理完成後,再以消息的形式發送給客戶端的。因而這裏出現了2個問題:
- 當前線程怎麼讓它「暫停」,等結果回來後,再向後執行?
- 正如前面所說,Socket通訊是一個全雙工的方式,若是有多個線程同時進行遠程方法調用,這時創建在client server之間的socket鏈接上會有不少雙方發送的消息傳遞,先後順序也多是亂七八糟的,server處理完結果後,將結果消息發送給client,client收到不少消息,怎麼知道哪一個消息結果是原先哪一個線程調用的?
分析源代碼,基本原理以下:
- 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()方法繼續執行就能拿到調用結果了),至此,整個過程結束。
這裏還須要畫一個大圖來描述,後面再補了
須要注意的是,這裏的
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,讓其惟一,而後傳遞給服務端,再服務端又回傳回來,這樣就知道結果是原先哪一個線程的了。