淺析dubbo原理和實現
一、Duboo基本概念解釋
Dubbo是一種分散式服務框架。 Webservice也是一種服務框架,但是webservice並不是分散式的服務框架,他需要結合F5實現負載均衡。因此,dubbo除了可以提供服務之外,還可以實現軟負載均衡。它還提供了兩個功能Monitor 監控中心和呼叫中心。這兩個是可選的,需要單獨配置。
Dubbo的計數架構圖如下:
我們解釋以下這個架構圖:
Consumer服務消費者,Provider服務提供者。Container服務容器。消費當然是invoke提供者了,invoke這條實線按照圖上的說明當然同步的意思了,多說一句,在實際呼叫過程中,Provider的位置對於Consumer來說是透明的,上一次呼叫服務的位置(IP地址)和下一次呼叫服務的位置,是不確定的。這個地方就是實現了軟負載。
Monitor這是一個監控,圖中虛線表明Consumer 和Provider通過非同步的方式傳送訊息至Monitor,Consumer和Provider會將資訊存放在本地磁碟,平均1min會發送一次資訊。Monitor在整個架構中是可選的(圖中的虛線並不是可選的意思),Monitor功能需要單獨配置,不配置或者配置以後,Monitor掛掉並不會影響服務的呼叫
二 分析原始碼,基本原理如下:
- 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()方法繼續執行就能拿到呼叫結果了),至此,整個過程結束。
- 當前執行緒怎麼讓它“暫停”,等結果回來後,再向後執行?
- 正如前面所說,Socket通訊是一個全雙工的方式,如果有多個執行緒同時進行遠端方法呼叫,這時建立在client server之間的socket連線上會有很多雙方傳送的訊息傳遞,前後順序也可能是亂七八糟的,server處理完結果後,將結果訊息傳送給client,client收到很多訊息,怎麼知道哪個訊息結果是原先哪個執行緒呼叫的?
三、遠端呼叫細節
服務提供者暴露一個服務的詳細過程:
上圖是服務提供者暴露服務的主過程:
首先ServiceConfig類拿到對外提供服務的實際類ref,然後將ProxyFactory類的getInvoker方法使用ref生成一個AbstractProxyInvoker例項,到這一步就完成具體服務到invoker的轉化。接下來就是Invoker轉換到Exporter的過程。
Dubbo處理服務暴露的關鍵就在Invoker轉換到Exporter的過程,下面我們以Dubbo和rmi這兩種典型協議的實現來進行說明:
Dubbo的實現:
Dubbo協議的Invoker轉為Exporter發生在DubboProtocol類的export方法,它主要是開啟socket偵聽服務,並接收客戶端發來的各種請求,通訊細節由dubbo自己實現。
Rmi的實現:
RMI協議的Invoker轉為Exporter發生在RmiProtocol類的export方法,他通過Spring或Dubbo或JDK來實現服務,通訊細節由JDK底層來實現。
服務消費者消費一個服務的詳細過程
上圖是服務消費的主過程:
首先ReferenceConfig類的init方法呼叫Protocol的refer方法生成Invoker例項。接下來把Invoker轉為客戶端需要的介面
釋出服務:
<!-- 使用dubbo釋出服務 -->
<!-- 提供方應用資訊,用於計算依賴關係 -->
<dubbo:application name="some-manager" />
<dubbo:registry protocol="zookeeper"
address="192.168.25.154:2181,192.168.25.154:2182,192.168.25.154:2183" />
<!-- 用dubbo協議在20880埠暴 露服務 -->
<dubbo:protocol name="dubbo" port="20880" />
<!-- 宣告需要暴露的服務介面 -->
<dubbo:service interface="com.taotao.service.ItemService" ref="itemServiceImpl" timeout=“3000”/>//設定服務超時時間:服務呼叫超時時間預設1秒
呼叫服務:
<!-- 引用dubbo服務 -->
<dubbo:application name="some-manager-web"/>
<dubbo:registry protocol="zookeeper" address="192.168.25.154:2181,192.168.25.154:2182,192.168.25.154:2183"/>
<dubbo:reference interface="com.taotao.service.ItemService" id="itemService" />
新增dubbo的依賴
加入dubbo相關的jar包。服務層、表現層都新增。
<!-- dubbo相關 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<!-- 排除依賴 -->
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
<exclusion>
<groupId>org.jboss.netty</groupId>
<artifactId>netty</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
</dependency>