前言
Apache Dubbo作為一款高性能的Java RPC框架愕宋,在國內(nèi)服務(wù)化體系的演進過程中扮演了一個非常重要的角色,被大量公司廣泛使用结榄。
臨近年關(guān)中贝,或許有小伙伴有著尋找新機會的想法,那么在面試過程中很可能會常常見到這樣一個問題:
你了解Dubbo嗎 ? 能不能講一講它的調(diào)用流程臼朗。
對于不了解的盆友而言邻寿,無疑會降低印象分蝎土;如果僅僅會使用,其實也不太夠绣否,最起碼我們要了解它的基本原理誊涯。
本文試圖從Dubbo使用者的角度上,結(jié)合流程圖和關(guān)鍵代碼把相應(yīng)知識點串聯(lián)起來蒜撮,回答我們上面的問題暴构。
一、服務(wù)提供者
從程序開發(fā)者的角度來看淀弹,我們要先有服務(wù)提供者丹壕。通常,我們在具體接口的實現(xiàn)上標(biāo)注Dubbo的Service
注解薇溃。
package com.viewscenes.producer.dubbo;
import org.apache.dubbo.config.annotation.Service;
import com.viewscenes.common.service.DubboUserService;
@Service
public class DubboUserServiceImpl implements DubboUserService {
}
這個實現(xiàn)類在Dubbo中對應(yīng)的解析類為ServiceBean
菌赖,它負責(zé)將這個實現(xiàn)對外暴露成一個服務(wù)。過程如下:
結(jié)合上圖來看沐序,我們可以說在提供者端琉用,暴露一個服務(wù)的過程如下:
首先,ServiceConfig
類引用對外提供服務(wù)的實現(xiàn)類ref (如DubboUserServiceImpl
) 策幼, 然后通過ProxyFactoty
接口的擴展實現(xiàn)類的getInvoker()
方法使用ref生成一個AbstractProxyInvoker
實例邑时,到此就完成了具體服務(wù)到Invoker
的轉(zhuǎn)化。
接下來特姐,通過Dubbo協(xié)議的export()
方法晶丘,將Invoker
轉(zhuǎn)化為Exporter
。那么在這里唐含,就會先啟動Netty Server
的監(jiān)聽浅浮,然后將服務(wù)注冊到服務(wù)注冊中心。
在這里捷枯,我們必須要注意的是滚秩,作為服務(wù)提供者端,已經(jīng)通過Netty開啟了TCP端口的監(jiān)聽淮捆。那么郁油,當(dāng)消費者調(diào)用的時候,通過一系列Netty Handler處理器攀痊,就會調(diào)用到DubboProtocol > ExchangeHandler.reply()
桐腌。
在這個方法里,就是一個反推的過程苟径。通過要調(diào)用的服務(wù)接口名稱哩掺,找到Exporter
,然后再獲取到Invoker
對象涩笤。
Invoker<?> getInvoker(Channel channel, Invocation inv) throws RemotingException {
int port = channel.getLocalAddress().getPort();
String path = inv.getAttachments().get(PATH_KEY);
String serviceKey = serviceKey(port, path, inv.getAttachments().get(VERSION_KEY), inv.getAttachments().get(GROUP_KEY));
DubboExporter<?> exporter = (DubboExporter<?>) exporterMap.get(serviceKey);
return exporter.getInvoker();
}
從上面的分析中我們已經(jīng)知道嚼吞,這里的Invoker
對象是根據(jù)服務(wù)實現(xiàn)類生成的一個AbstractProxyInvoker
實例。它最終會調(diào)用到wrapper.invokeMethod()
方法蹬碧。這里的wrapper
類是通過Javassist
生成的舱禽,在內(nèi)存中的類,它的核心方法長這樣:
Dubbo
會給每個服務(wù)提供者的實現(xiàn)生成一個Wrapper
類恩沽。當(dāng)接收到消費方的請求后誊稚,根據(jù)傳遞的方法名和參數(shù),Wrapper
類調(diào)用服務(wù)提供者的接口類實現(xiàn)即可罗心。這樣做的目的主要是為了減少反射的調(diào)用里伯。
二、服務(wù)消費者
在服務(wù)消費者端渤闷,我們直接引用一個接口即可疾瓮。
@Reference
DubboUserService userService;
或許你可能要問,為啥只注入了這么一個普通的接口飒箭,就可以調(diào)用到遠端的服務(wù)呢 狼电?
我們想想在 Mybatis中的Dao接口和XML文件里的SQL是如何建立關(guān)系的? 這個問題中弦蹂,它們是怎么關(guān)聯(lián)起來的呢 肩碟?
說穿了還是Spring
的功勞,或者說是Spring FactoryBean
的功勞凸椿。
在Dubbo
中削祈,標(biāo)注了@Reference
的接口,都會被當(dāng)成一個Factory Bean
脑漫,這個Bean一般都會返回一個代理對象髓抑,來屏蔽底層一些復(fù)雜的操作。比如Mybatis
里的mapper接口和xml文件關(guān)聯(lián)窿撬,Dubbo
中的網(wǎng)絡(luò)通信等启昧。
我們還是先來通過一張圖看看消費者端的具體過程:
結(jié)合上圖來看,我們總結(jié)下服務(wù)引用的過程:
Reference
注解標(biāo)注的Dubbo
接口劈伴,會被注冊成FactoryBean
密末,并最終返回一個代理對象。
在創(chuàng)建代理的過程中跛璧,會調(diào)用其他方法構(gòu)建以及合并 Invoker
實例严里。
首先,調(diào)用DubboProtocol
的refer方法追城,返回DubboInvoker
對象刹碾。在這里,比較重要的是獲取客戶端實例座柱。比如NettyClient
迷帜,Dubbo
要依靠它來進行網(wǎng)絡(luò)通信物舒。
然后,還需要將多個服務(wù)提供者實例合并成一個戏锹,這是集群容錯機制的實現(xiàn)冠胯。
最后,通過JavassistProxyFactory
創(chuàng)建代理并返回锦针。在這里荠察,它的處理器是InvokerInvocationHandler
,這就意味著奈搜,當(dāng)我們在消費者端調(diào)用一個Dubbo
接口的時候悉盆,實際上會調(diào)用到InvokerInvocationHandler.invoke()
方法,在這里面Dubbo
完成了譬如集群容錯馋吗、負載均衡焕盟、調(diào)用遠程方法的一系列動作。
Dubbo消費者發(fā)送請求的時候耗美,最終會調(diào)用到DubboInvoker
中的方法京髓。在這里,會完成具體的請求邏輯商架,比如發(fā)送請求數(shù)據(jù)堰怨。
final class HeaderExchangeChannel implements ExchangeChannel {
//創(chuàng)建請求消息對象
Request req = new Request();
req.setVersion(Version.getProtocolVersion());
req.setTwoWay(true);
req.setData(request);
//創(chuàng)建Future,用于獲取返回結(jié)果
DefaultFuture future = DefaultFuture.newFuture(this.channel, req, timeout, executor);
try {
//通過Netty客戶端發(fā)送數(shù)據(jù)
this.channel.send(req);
return future;
} catch (RemotingException var7) {
future.cancel();
throw var7;
}
}