03.02 本篇對 Dubbo的服務暴露流程,核心點 Invoker 後轉化到 Export

本篇對 Dubbo的服務暴露流程,核心點 Invoker 後轉化到 Export

前言

本文 Dubbo 使用版本 2.7.5

Dubbo 通過使用 dubbo:service配置或 @service在解析完配置後進行服務暴露,供服務消費者消費。

Dubbo 的服務暴露有兩種:

  • 遠程暴露
  • 本地暴露

可以通過 scope顯式指定暴露方式:

  • none 不暴露
  • remote 遠程暴露
  • local 本地暴露

服務暴露流程

下面是一個服務暴露的流程圖:

本篇對 Dubbo的服務暴露流程,核心點 Invoker 後轉化到 Export

ProxyFactory 是動態代理,用來創建 Invoker 對象,實現代理使用 JavassistProxyFactory和 JdkProxyFactory。

Invoker 是一個服務對象實例,Dubbo 框架的實體域。它可以是一個本地的實現,一個遠程的實現或一個集群的實現,可以向它發起 Invoker 調用。

Protocol 是服務域,負責 Invoker 的生命週期管理,是 Invoker 暴露和引用的主要功能入口,對應該類的 export和 refer方法。

Exporter 是根據不同協議暴露 Invoker 進行封裝的類,它會根據不同的協議頭進行識別(比如:registry://和 dubbo://),調用對應 XXXProtocol的 export()方法。

從上圖中可以看到,Dubbo 中服務暴露分為兩個大步驟:第一步通過代理將服務實例轉換成 Invoker,這就是通過我們常用的反射實現。第二步將 Invoker 根據具體的協議轉換成 Exporter,這是就是我們要分析的核心。從這裡可以看到 Dubbo 服務對象都是圍繞 Invoker 進行工作。

遠程暴露

服務遠程暴露從字面上理解,就是將服務跨網絡進行遠程通信,並非同一 JVM 中的服務進行調用。服務最後都是轉換成 org.apache.dubbo.config.spring.ServiceBean,它的UML類圖:

本篇對 Dubbo的服務暴露流程,核心點 Invoker 後轉化到 Export

ServiceBean繼承自 ServiceConfig,服務在 ServiceConfig#doExportUrls根據不同協議進行暴露。

本篇對 Dubbo的服務暴露流程,核心點 Invoker 後轉化到 Export

通過獲取所有註冊中心實例(registryURLs)後,進行依次暴露,暴露操作在 doExportUrlsFor1Protocol中。

<code>private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {    Map<string> map = new HashMap<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));                    // 獲取監控URL                    URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);                    if (monitorUrl != null) {                        // 追加監控上報地址,在攔截器上報數據                        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);                    }                    // 將服務對象轉換成 Invoker                    Invoker> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(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 = new DelegateProviderMetaDataInvoker(invoker, this);                // 直接暴露服務                Exporter> exporter = protocol.export(wrapperInvoker);                exporters.add(exporter);            }            /**             * 存儲Dubbo服務的元數據,元數據可以存儲在遠端配置中心和本地,默認是存儲在本地             * @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);}/<string>/<string>/<code>

上面是代碼片段為暴露服務的核心,可以看到 scope 由三個值控制是否暴露和遠程或本地暴露,默認遠程和本地都暴露。在遠程調用中,分為使用註冊中心暴露直接暴露(默認dubbo協議),它們之間的區別在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

無註冊中心的直接暴露服務。有註冊中心的先創建註冊中心,再得到 export 的服務地址,然後暴露服務,當服務暴露成功後把服務元數據註冊到註冊中心。

代碼中 protocol#export會根據服務 url 的請求頭進行區分不同 XXXProtocol#export的邏輯,比如。目前 Dubbo 中有以下幾種:

本篇對 Dubbo的服務暴露流程,核心點 Invoker 後轉化到 Export

本地暴露

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

本篇對 Dubbo的服務暴露流程,核心點 Invoker 後轉化到 Export

本地暴露會指定 injvm 協議,並且 host 指定為本地 127.0.0.1和端口號為0。protocol.export 調用 InjvmProtocol#export 實現:

<code>@Override    public  Exporter export(Invoker invoker) throws RpcException {        return new InjvmExporter(invoker, invoker.getUrl().getServiceKey(), exporterMap);    }/<code>

export 中返回了 InjvmExporter 實例化對象。

<code>class InjvmExporter 
extends AbstractExporter { private final String key; private final Map<string>> exporterMap; InjvmExporter(Invoker invoker, String key, Map<string>> exporterMap) { super(invoker); this.key = key; this.exporterMap = exporterMap; exporterMap.put(key, this); } @Override public void unexport() { super.unexport(); exporterMap.remove(key); }}/<string>/<string>/<code>

本地暴露就比較簡單,將 Invoker 直接保存在 InjvmExporter 的 exporterMap 中。

最後

本篇對 Dubbo 的服務暴露流程進行了分析,核心點就是開篇圖中的得到 Invoker 後轉化到 Export。


分享到:


相關文章: