Dubbo之服務暴露
Python實戰(zhàn)社群
Java實戰(zhàn)社群
長按識別下方二維碼,按需求添加
掃碼關(guān)注添加客服
進Python社群▲
掃碼關(guān)注添加客服
進Java社群▲
作者丨ytao
來源丨ytao
前言
本文 Dubbo 使用版本 2.7.5
Dubbo 通過使用 dubbo:service配置或 @service在解析完配置后進行服務暴露,供服務消費者消費。
Dubbo 的服務暴露有兩種:
遠程暴露
本地暴露
可以通過 scope顯式指定暴露方式:
none 不暴露
remote 遠程暴露
local 本地暴露
服務暴露流程
下面是一個服務暴露的流程圖:

ProxyFactory 是動態(tài)代理,用來創(chuàng)建 Invoker 對象,實現(xiàn)代理使用 JavassistProxyFactory和 JdkProxyFactory。
Invoker 是一個服務對象實例,Dubbo 框架的實體域。它可以是一個本地的實現(xiàn),一個遠程的實現(xiàn)或一個集群的實現(xiàn),可以向它發(fā)起 Invoker 調(diào)用。
Protocol 是服務域,負責 Invoker 的生命周期管理,是 Invoker 暴露和引用的主要功能入口,對應該類的 export和 refer方法。
Exporter 是根據(jù)不同協(xié)議暴露 Invoker 進行封裝的類,它會根據(jù)不同的協(xié)議頭進行識別(比如:registry://和 dubbo://),調(diào)用對應 XXXProtocol的 export()方法。
從上圖中可以看到,Dubbo 中服務暴露分為兩個大步驟:第一步通過代理將服務實例轉(zhuǎn)換成 Invoker,這就是通過我們常用的反射實現(xiàn)。第二步將 Invoker 根據(jù)具體的協(xié)議轉(zhuǎn)換成 Exporter,這是就是我們要分析的核心。從這里可以看到 Dubbo 服務對象都是圍繞 Invoker 進行工作。
遠程暴露
服務遠程暴露從字面上理解,就是將服務跨網(wǎng)絡進行遠程通信,并非同一 JVM 中的服務進行調(diào)用。服務最后都是轉(zhuǎn)換成 org.apache.dubbo.config.spring.ServiceBean,它的UML類圖:

ServiceBean繼承自 ServiceConfig,服務在 ServiceConfig#doExportUrls根據(jù)不同協(xié)議進行暴露。

通過獲取所有注冊中心實例(registryURLs)后,進行依次暴露,暴露操作在 doExportUrlsFor1Protocol中。
privatevoid doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, ListregistryURLs) {
Map<String, String> map = newHashMap<String, String>();
// 配置信息存入 map
.....
// 獲取服務URL
String host = findConfigedHosts(protocolConfig, registryURLs, map);
Integer port = findConfigedPorts(protocolConfig, name, map);
URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/"+ path).orElse(path), map);
.....
String scope = url.getParameter(SCOPE_KEY);
// 如果 scope 配置為 none,則服務不進行暴露
if(!SCOPE_NONE.equalsIgnoreCase(scope)) {
// 本地暴露
if(!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
exportLocal(url);
}
// 遠程暴露
if(!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
// 判斷是否有注冊中心
if(CollectionUtils.isNotEmpty(registryURLs)) {
for(URL registryURL : registryURLs) {
//if protocol is only injvm ,not register
if(LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
continue;
}
url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
// 獲取監(jiān)控URL
URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
if(monitorUrl != null) {
// 追加監(jiān)控上報地址,在攔截器上報數(shù)據(jù)
url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
}
// 日志打印
if(logger.isInfoEnabled()) {
if(url.getParameter(REGISTER_KEY, true)) {
logger.info("Register dubbo service "+ interfaceClass.getName() + " url "+ url + " to registry "+ registryURL);
} else{
logger.info("Export dubbo service "+ interfaceClass.getName() + " to url "+ url);
}
}
// For providers, this is used to enable custom proxy to generate invoker
String proxy = url.getParameter(PROXY_KEY);
if(StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(PROXY_KEY, proxy);
}
// 將服務對象轉(zhuǎn)換成 Invoker
Invoker> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = newDelegateProviderMetaDataInvoker(invoker, this);
// 暴露服務,向注冊中心注冊服務,進入對應的 RegistryProtocol
Exporter> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
} else{ // 沒有注冊中心時
if(logger.isInfoEnabled()) {
logger.info("Export dubbo service "+ interfaceClass.getName() + " to url "+ url);
}
Invoker> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = newDelegateProviderMetaDataInvoker(invoker, this);
// 直接暴露服務
Exporter> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
/**
* 存儲Dubbo服務的元數(shù)據(jù),元數(shù)據(jù)可以存儲在遠端配置中心和本地,默認是存儲在本地
* @since 2.7.0
* ServiceData Store
*/
WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE));
if(metadataService != null) {
metadataService.publishServiceDefinition(url);
}
}
}
this.urls.add(url);
}
上面是代碼片段為暴露服務的核心,可以看到 scope 由三個值控制是否暴露和遠程或本地暴露,默認遠程和本地都暴露。在遠程調(diào)用中,分為使用注冊中心暴露和直接暴露(默認dubbo協(xié)議),它們之間的區(qū)別在url上:
無注冊中心:dubbo://192.168.3.19:20880/xxxx
有注冊中心:registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=provider&dubbo=2.0.2&export=dubbo://192.168.3.19:20880/xxxx
無注冊中心的直接暴露服務。有注冊中心的先創(chuàng)建注冊中心,再得到 export 的服務地址,然后暴露服務,當服務暴露成功后把服務元數(shù)據(jù)注冊到注冊中心。
代碼中 protocol#export會根據(jù)服務 url 的請求頭進行區(qū)分不同 XXXProtocol#export的邏輯,比如。目前 Dubbo 中有以下幾種:

本地暴露
同一個應用中,可能既要提供服務遠程暴露給其他應用引用,也要給自身提供引用。如果只提供遠程暴露的話,當自身應用需要引用自身的服務時,需要通過遠程通信訪問,那么這大大浪費網(wǎng)絡資源。這是就需要用 injvm 協(xié)議暴露,就是我們所說的本地暴露,無需跨網(wǎng)絡遠程通信,可以更好的節(jié)省資源。通過上面代碼中,我們知道本地暴露調(diào)用的是 ServiceConfig#exportLocal方法。

本地暴露會指定 injvm 協(xié)議,并且 host 指定為本地 127.0.0.1和端口號為0。protocol.export 調(diào)用 InjvmProtocol#export 實現(xiàn):
@Override
publicExporter export( Invokerinvoker) throwsRpcException{
returnnewInjvmExporter(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}
export 中返回了 InjvmExporter 實例化對象。
classInjvmExporterextendsAbstractExporter {
privatefinalString key;
privatefinalMap<String, Exporter>> exporterMap;
InjvmExporter(Invokerinvoker, String key, Map<String, Exporter>> exporterMap) {
super(invoker);
this.key = key;
this.exporterMap = exporterMap;
exporterMap.put(key, this);
}
@Override
publicvoid unexport() {
super.unexport();
exporterMap.remove(key);
}
}
本地暴露就比較簡單,將 Invoker 直接保存在 InjvmExporter 的 exporterMap 中。


近期精彩內(nèi)容推薦:??


