Dubbo 系列(05-1)服务发布

Dubbo 系列(05-1)服务发布

Spring Cloud Alibaba 系列目录 - Dubbo 篇

1. 背景介绍

相关文档推荐:

  1. Dubbo 实战 - API 配置
  2. Dubbo 源码解析 - 服务暴露

本章主要研究一下 Dubbo 服务暴露和服务引入的过程。Duboo 服务暴露和引用分别是从 ServiceConfig 和 ReferenceConfig 开始,代码位于 dubbo-config-api 工程中。Spring 容器中为了适配,分别扩展 了ServiceBean 和 ReferenceBean,这部分代码在 dubbo-config-spring 工程中。

图1 Dubbo 服务暴露和引入类继承图

本节先分析服务暴露的整个流程,代码围绕 ServiceConfig 展开。

1.1 服务暴露整体机制

图2 Dubbo服务暴露整体机制

注:图片来源《深入理解Apache Dubbo与实战》

从整体上看,Dubbo 服务暴露分为两个大部分:

  1. 将服务实例 ref 通过动态代理转换成 Invoker 实例。Invoker 是 Dubbo 的核心领域模型,它代表一个可执行实体,可能是一个本地实例,也可能是远程实例,也可能是集群实例,还可能是本地伪装实例。
  2. 将 Invoker 通过具体的协议(eg: Dubbo)转换成 Exporter。

2. 源码分析

在服务暴露整体机制中只介绍了服务暴露的核心逻辑,实际上在服务暴露之前还要进行参数校验,服务暴露时同时也需要进行服务注册,用于服务发现。Dubbo 服务导出大致可分为三个部分,本篇文章将会对这三个部分代码进行详细的分析。

  1. 前置工作,主要工作:一是配置检测;二是注册中心加载;三是组装 URL。

  2. 导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程。

  3. 向注册中心注册服务,用于服务发现。

public synchronized void export() {
    // 1. 校验配置参数
    checkAndUpdateSubConfigs();
    // 2. 服务是否已经发布,如果已经发布,直接返回
    if (!shouldExport()) {
        return;
    }

    // 3. doExport 暴露服务
    if (shouldDelay()) {
        DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
    } else {
        doExport();
    }
}

private void doExportUrls() {
    // 1. 加载注册中心url,支持多注册中心
    //    URL相当于总线:registry://172.16.16.11:2181/org.apache.dubbo.registry.RegistryService?key=value
    List<URL> registryURLs = loadRegistries(true);
    for (ProtocolConfig protocolConfig : protocols) {
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}

总结: checkAndUpdateSubConfigs 方法用于配置检测,之后调用 doExportUrls 进行服务发布。在发布前需要先通过 loadRegistries 加载注册中心,之后遍历所有的 ProtocolConfig 依次发布对应协议的服务。doExportUrlsFor1Protocol 才是服务发布的核心逻辑,但这个方法特别长,前半段主要是组装服务 URL,之后主要进行服务发布。

2.1 前置工作

2.1.1 配置检测

public void checkAndUpdateSubConfigs() {
    // 1. 如果<dubbo:service> 标签没有指定,则使用全局的默认配置
    completeCompoundConfigs();
    // 2. ConfigCenter 配置中心默认启动
    startConfigCenter();
    // 3. 分别校验 provider、protocol、application、registry 配置
    checkDefault();
    checkProtocol();
    checkApplication();
    if (!isOnlyInJvm()) {
        checkRegistry();
    }
    this.refresh();
    checkMetadataReport();

    // 4. 检测 <dubbo:service> 标签的 interface 属性合法性
    if (StringUtils.isEmpty(interfaceName)) {
        throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
    }

    // 5. 检测并处理泛化服务和普通服务类 <dubbo:service ref="xxx" >
    if (ref instanceof GenericService) {
        interfaceClass = GenericService.class;
        if (StringUtils.isEmpty(generic)) {
            generic = Boolean.TRUE.toString();
        }
    } else {
        // 检测接口interfaceClass是否存在
        try {
            interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                                           .getContextClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        checkInterfaceAndMethods(interfaceClass, methods);
        checkRef();
        generic = Boolean.FALSE.toString();
    }
    
    // 6.1 解析本地存根local配置对应的类名 <dubbo:service ... local="true" >
    if (local != null) {
        if ("true".equals(local)) {
            local = interfaceName + "Local";
        }
        Class<?> localClass;
        try {
            localClass = ClassUtils.forNameWithThreadContextClassLoader(local);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        if (!interfaceClass.isAssignableFrom(localClass)) {
            throw new IllegalStateException();
        }
    }
    // 6.2 解析本地存根stub配置对应的类名 <dubbo:service ... stub="true" >
    if (stub != null) {
        if ("true".equals(stub)) {
            stub = interfaceName + "Stub";
        }
        Class<?> stubClass;
        try {
            stubClass = ClassUtils.forNameWithThreadContextClassLoader(stub);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        if (!interfaceClass.isAssignableFrom(stubClass)) {
            throw new IllegalStateException();
        }
    }
    // 6.3 检测本地存根的类是否存在
    checkStubAndLocal(interfaceClass);
    // 7. 检测mock配置。<dubbo:service ... mock="true" >
    checkMock(interfaceClass);
}

总结: 配置检查代码比较多。下面对配置检查的逻辑进行简单的总结,如下:

  1. 检测 <dubbo:service> 是否指定 ProviderConfig、ApplicationConfig 等配置,如果没有,使用全局默认的配置。
  2. 启动配置中心 ConfigCenter。
  3. 检测 ProviderConfig、ApplicationConfig 等核心配置类对象是否为空。
  4. 检测 <dubbo:service interface="xxx"> 标签的 interface 属性合法性,不合法则抛出异常。
  5. 检测并处理泛化服务和普通服务类。
  6. 检测 local 和 stub 本地存根配置,并进行相应的处理。
  7. 检测 mock 配置是否无误。

配置检查并非本文重点,因此这里不打算对 checkAndUpdateSubConfigs 方法进行分析。

2.1.2 加载注册中心

export -> doExport -> doExportUrls -> loadRegistries 方法加载注册中心,也就是将 RegistryConfig 配置解析成 registryUrl。在 <dubbo:registry address="nacos://192.168.139.101:8848"/> 中 address 属性可以配置多个,服务发布是会同时向这些注册中心注册,其中 "N/A" 表示不需要注册中心,直接点对点通信。

/**
 * 1. 构建参数映射集合,也就是 map
 * 2. 构建注册中心链接列表
 * 3. 遍历链接列表,并根据条件决定是否将其添加到 registryList 中
 * @param provider表示是否是服务提供者
 */
protected List<URL> loadRegistries(boolean provider) {
    List<URL> registryList = new ArrayList<URL>();
    if (CollectionUtils.isNotEmpty(registries)) {
        for (RegistryConfig config : registries) {
            String address = config.getAddress();
            // 若 address 为空,则将其设为 0.0.0.0
            if (StringUtils.isEmpty(address)) {
                address = ANYHOST_VALUE;
            }
            // 1. 构建注册中心地址 URL,NO_AVAILABLE="N/A"表示无注册中心
            if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
                // 2. 构建 URL 的参数
                Map<String, String> map = new HashMap<String, String>();
                appendParameters(map, application);		// 添加application
                appendParameters(map, config);			// 添加RegistryConfig
                map.put(PATH_KEY, RegistryService.class.getName());	// 添加path
                appendRuntimeParameters(map);			// 添加pid
                if (!map.containsKey(PROTOCOL_KEY)) {	// 默认注册中心采用dubbo
                    map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
                }

                // 3. 根据地址address和参数map,构建注册中心URL
                //    address 可以配置多个地址,用 '|' 或 ';' 隔开
                List<URL> urls = UrlUtils.parseURLs(address, map);

                for (URL url : urls) {
                    // 4. 构建注册中心地址
                    //    eg: registry://com.foo.xxxService?registry=zookeeper
                    url = URLBuilder.from(url)
                        .addParameter(REGISTRY_KEY, url.getProtocol())	// 设置registry参数
                        .setProtocol(REGISTRY_PROTOCOL)		// 设置协议为registry
                        .build();
                    // 5. 判断是否添加该注册中心:默认添加,但提供了URL参数(registry/subscribe)会取消注册
                    // 5.1 如果是服务提供者,registry=true 时注册,默认注册
                    // 5.2 如果是非服务提供者,subscribe=true 时注册,默认注册
                    if ((provider && url.getParameter(REGISTER_KEY, true))
                        || (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
                        registryList.add(url);
                    }
                }
            }
        }
    }
    return registryList;
}

总结: loadRegistries 方法主要包含如下的逻辑:

  1. 遍历注册中心,分别构建 registryUrl,其中 address="N/A" 表示无注册中心,若为空则默认为 0.0.0.0
  2. 构建参数映射集合,也就是 map
  3. 构建注册中心链接列表,一个 address 可以配置多个地址
  4. 遍历链接列表,构建 registryUrl,并根据条件决定是否将其添加到 registryList 中,默认都会添加。

关于多协议多注册中心导出服务就先分析到这,代码不是很多,接下来分析 URL 组装过程。

2.1.3 构建服务 URL

加载注册中心地址后,会遍历所有的 ProtocolConfig,依次调用 doExportUrlsFor1Protocol(protocolConfig, registryURLs) 发布服务。doExportUrlsFor1Protocol 前半段主要是构建服务的 URL。

private void doExportUrlsFor1Protocol(
    ProtocolConfig protocolConfig, List<URL> registryURLs) {
    // 1. 如果协议名为空,或空串,则将协议名变量设置为 dubbo
    String name = protocolConfig.getName();
    if (StringUtils.isEmpty(name)) {
        name = DUBBO;
    }

    // 2. 构建 url 参数。添加 side、版本、时间戳以及进程号等信息到 map 中
    Map<String, String> map = new HashMap<String, String>();
    map.put(SIDE_KEY, PROVIDER_SIDE);	// side

    appendRuntimeParameters(map);		// 版本、时间戳以及进程号
    // appendParameters方法将metrics作为bean添加到Map中,如果有第三个参数则表示前缀
    appendParameters(map, metrics);		// metrics
    appendParameters(map, application);	// applicationConfig
    appendParameters(map, module);		// moduleConfig
    appendParameters(map, provider);	// providerConfig
    appendParameters(map, protocolConfig);// protocolConfig
    appendParameters(map, this);		// serviceConfig
    
    // 3. 解析MethodConfig,对应<dubbo:method>标签
    //    <dubbo:service><dubbo:method></dubbo:method></dubbo:service>
    if (CollectionUtils.isNotEmpty(methods)) {
        for (MethodConfig method : methods) {
            appendParameters(map, method, method.getName());	// methodConfig
            String retryKey = method.getName() + ".retry";
            if (map.containsKey(retryKey)) {
                String retryValue = map.remove(retryKey);
                if ("false".equals(retryValue)) {
                    map.put(method.getName() + ".retries", "0");
                }
            }
    		// 4. 解析ArgumentConfig,对应<dubbo:method>标签
            List<ArgumentConfig> arguments = method.getArguments();
            ... 解析ArgumentConfig ...
        } // end of methods for
    }

    // 5. 检测 generic 是否为 "true",并根据检测结果向 map 中添加不同的信息
    if (ProtocolUtils.isGeneric(generic)) {
        map.put(GENERIC_KEY, generic);
        map.put(METHODS_KEY, ANY_VALUE);
    } else {
        String revision = Version.getVersion(interfaceClass, version);
        if (revision != null && revision.length() > 0) {
            map.put(REVISION_KEY, revision);
        }
        // 为接口生成包裹类 Wrapper,Wrapper 中包含了接口的详细信息,比如接口方法名数组,字段信息等
        String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
        // 添加方法名到 map 中,如果包含多个方法名,则用逗号隔开,比如 method = init,destroy
        if (methods.length == 0) {
            map.put(METHODS_KEY, ANY_VALUE);
        } else {
            // 将接口的所有方法用 "," 拼接后作为参数 methods 的值
            map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
        }
    }
    // 6. 添加 token 到 map 中,默认随机生成 UUID.randomUUID()
    if (!ConfigUtils.isEmpty(token)) {
        if (ConfigUtils.isDefault(token)) {
            map.put(TOKEN_KEY, UUID.randomUUID().toString());
        } else {
            map.put(TOKEN_KEY, token);
        }
    }
    // 7. 根据 host+port 组装 URL
    String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
    Integer port = this.findConfigedPorts(protocolConfig, name, map);
    URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);

    // 8. TODO 动态配置
    if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
        .hasExtension(url.getProtocol())) {
        // 加载 ConfiguratorFactory,并生成 Configurator 实例,然后通过实例配置 url
        url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
            .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
    }

    ... 9. 之后是服务发布过程 ....
}

总结: doExportUrlsFor1Protocol 方法特别长,上述代码主要是服务 URL 的构建过程,忽略了第四步解析ArgumentConfig的步骤和第九步服务发布的逻辑。

  1. 如果没有设置协议,默认为 dubbo 协议发布服务。
  2. 设置服务 URL 的 metrics、applicationConfig、moduleConfig、providerConfig、protocolConfig 等参数信息。appendParameters(map, application) 方法会将 JavaBean 的信息存储到 Map 中作为 URL 的参数。
  3. 解析指定方法的 MethodConfig。
  4. 解析方法的参数配置信息 ArgumentConfig。代码略。
  5. 解析泛化调用和普通调用。如果是普通通用,将接口中的方法用 "," 拼接后作为参数 methods 的值。
  6. token 参数设置。默认为随机值。
  7. 至此,参数的配置已经完成,可以组装服务的 URL。
  8. 此时,外部化配置可以覆盖本地的配置信息。
  9. 服务发布。代码略。

如下,服务发布最终生成的 URL:

dubbo://192.168.139.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-provider&bean.name=org.apache.dubbo.demo.DemoService&bind.ip=192.168.139.1&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=34088&qos.port=22222&register=true&release=&side=provider&timestamp=1571475529431

至此,前置工作已经准备完毕,注册中心 loadRegistries 已经加载完成,服务的 URL 也已经准备完毕,只剩下服务发布与注册了。

2.2 服务发布

2.2.1 doExportUrlsFor1Protocol

private void doExportUrlsFor1Protocol(
    ProtocolConfig protocolConfig, List<URL> registryURLs) {
	// url已经组装完成,下面开始完成服务发布
    URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
    
    // scope="none"、"remote"、"local" 分别表示不发布、发布到本地以及发布到远程
    String scope = url.getParameter(SCOPE_KEY);
    if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
        // 1. scope != remote,发布到本地,也就是保存到本地的 Map 集合中
        if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
            exportLocal(url);
        }
        // 2. scope != local,发布到远程
        if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
            // 3. 存在注册中心,需要将服务注册到注册中心
            if (CollectionUtils.isNotEmpty(registryURLs)) {
                for (URL registryURL : registryURLs) {
                    // 3.1 protocol=local 则忽略
                    if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
                        continue;
                    }
                    // 3.2 设置url的dynamic参数
                    url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
                    // 3.3 设置url的monitor参数,监视器链接
                    URL monitorUrl = loadMonitor(registryURL);
                    if (monitorUrl != null) {
                        url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
                    }

                    // 3.4 设置url的proxy参数,动态代理方式 jdk or cglib
                    String proxy = url.getParameter(PROXY_KEY);
                    if (StringUtils.isNotEmpty(proxy)) {
                        registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                    }
                    // 3.5 为服务提供类(ref)生成 Invoker
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                    // DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfig
                    DelegateProviderMetaDataInvoker wrapperInvoker =
                        new DelegateProviderMetaDataInvoker(invoker, this);

                    // 3.6 导出服务,并生成 Exporter RegistryProtocol
                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
            // 4. 不存在注册中心,仅发布服务,同 3.5 ~ 3.6
            } else {
                Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
                DelegateProviderMetaDataInvoker wrapperInvoker =
                    new DelegateProviderMetaDataInvoker(invoker, this);

                Exporter<?> exporter = protocol.export(wrapperInvoker);
                exporters.add(exporter);
            }
            
            // 5. 暴露服务元信息,sine 2.7.0
            MetadataReportService metadataReportService = null;
            if ((metadataReportService = getMetadataReportService()) != null) {
                metadataReportService.publishProvider(url);
            }
        }
    }
    this.urls.add(url);
}

总结: doExportUrlsFor1Protocol 后半段主要是服务发布,服务发布以分为远程发布和本地发布。先关注一个参数 scope,可以有三个值 scope="none"、"remote"、"local" 分别表示不发布、发布到本地以及发布到远程,默认 scope=null,也就是说默认本地发布和远程同时发布这个服务。

  1. 本地发布服务:此时 scope != remote。调用 exportLocal 发布本地服务。
  2. 远程服务发布:此时 scope != local。远程发布以分两种情况:一是有注册中心;二是没有注册中心。
  3. 将 ref 动态代理生成 Invoker。如果有注册中心,首先要进一步配置服务 URL 的 dynamic(是否持久化)、monitor(监控地址)、proxy(动态代理方式) 参数,最后调用 PROXY_FACTORY 生成 Invoker。如果没有注册中心则直接调用 PROXY_FACTORY 生成 Invoker。
  4. 将 Invoker 按对应的协议进行发布服务。如果有注册中心 protocol 对应的实现是 RegistryProtocol。如果没有注册中心则 protocol 对应的实现是对应的协议实现,如 DubboProtocol。

有注册中心和无注册中心的 URL 地址区别如下:

# 远程发布 registryURL
registry://192.168.139.101:8848/org.apache.dubbo.registry.RegistryService?application=dubbo-provider&dubbo=2.0.2&pid=30192&qos.port=22222&registry=nacos&timestamp=1571479622848
# 本地发布 registryURL
dubbo://192.168.139.101:28880/org.apache.dubbo.DemoService?application=dubbo-provider&dubbo=2.0.2&pid=30192&qos.port=22222&timestamp=1571479622848

2.2.2 本地服务发布

private void exportLocal(URL url) {
    // 创建 URL 的协议头 injvm
    URL local = URLBuilder.from(url)
        .setProtocol(LOCAL_PROTOCOL)
        .setHost(LOCALHOST_VALUE)
        .setPort(0)
        .build();
    Exporter<?> exporter = protocol.export(
        PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
    exporters.add(exporter);
}

exportLocal 方法比较简单,先创建一个新的 URL 并将协议头、主机名以及端口设置成新的值。然后创建 Invoker,并调用 InjvmProtocol 的 export 方法导出服务。下面我们来看一下 InjvmProtocol 的 export 方法都做了哪些事情。

public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    // 创建 InjvmExporter
    return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}
InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) {
    super(invoker);
    this.key = key;
    this.exporterMap = exporterMap;
    exporterMap.put(key, this);
}

如上,InjvmProtocol 的 export 方法仅创建了一个 InjvmExporter。到此导出服务到本地就分析完了,接下来,我们继续分析导出服务到远程的过程。

注意: exporterMap 是父类 AbstractProtocol 中的定义, exporterMap = new ConcurrentHashMap<String, Exporter<?>>(),用于保存每种协议发布的的服务,key 为 URL.serviceKey,value 则为 Exporter,这样消费者就可以通过 协议 + URL#serviceKey 查找到对应的服务了。

2.2.3 远程服务发布

与本地服务发布相比,远程服务发布的过程要复杂不少,其包含了服务导出与服务注册两个过程。下面开始分析,我们把目光移动到 RegistryProtocol 的 export 方法上。

@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    // registryUrl:获取注册中心url     nacos://ip:port?key=value
    URL registryUrl = getRegistryUrl(originInvoker);
    // providerUrl:获取本地暴露服务的地址,export属性   dubbo://ip:port/interface?key=value
    URL providerUrl = getProviderUrl(originInvoker);

    // overrideSubscribeUrl:订阅 override 数据
    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
    final OverrideListener overrideSubscribeListener =
        new OverrideListener(overrideSubscribeUrl, originInvoker);
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);

    // 外部化配置。根据 override 信息覆盖 providerUrl
    providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
    
    // 1. 核心代码,发布服务
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

    // 2. 根据注册中心url获取对应的注册中心Registry
    final Registry registry = getRegistry(originInvoker);
    final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
    ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(
        originInvoker, registryUrl, registeredProviderUrl);
    boolean register = registeredProviderUrl.getParameter("register", true);
    // 核心代码,根据 register 的值决定是否注册服务
    if (register) {
        register(registryUrl, registeredProviderUrl);
        providerInvokerWrapper.setReg(true);
    }

    // Deprecated! Subscribe to override rules in 2.6.x or before.
    // 3. 向注册中心进行订阅 override 数据
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);

    // 4. 创建并返回 DestroyableExporter
    exporter.setRegisterUrl(registeredProviderUrl);
    exporter.setSubscribeUrl(overrideSubscribeUrl);
    return new DestroyableExporter<>(exporter);
}

总结: 上面代码看起来比较复杂,主要做如下一些操作:

  1. 发布服务:调用 doLocalExport 导出服务。核心。
  2. 注册服务:调用 register 向注册中心注册服务。核心。
  3. 订阅 override :调用 subscribe 向注册中心进行订阅 override 数据。
  4. 返回 exporter:创建并返回 DestroyableExporter。

在以上操作中,除了创建并返回 DestroyableExporter 没什么难度外,其他几步操作都不是很简单。这其中,导出服务和注册服务是本章要重点分析的逻辑。 订阅 override 数据并非本文重点内容,后面会简单介绍一下。

补充:Dubbo URL

在讲解代码之前,先分析一下上面出现的几个 URL 信息:originInvoker.URL、registryUrl、providerUrl、overrideSubscribeUrl

# originInvoker.URL
registry://192.168.139.101:8848/org.apache.dubbo.registry.RegistryService?application=dubbo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F192.168.139.1%3A20880%2Forg.apache.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddubbo-provider%26bean.name%3Dorg.apache.dubbo.demo.DemoService%26bind.ip%3D192.168.139.1%26bind.port%3D20880%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D23412%26qos.port%3D22222%26register%3Dtrue%26release%3D%26side%3Dprovider%26timestamp%3D1571480752244&pid=23412&qos.port=22222&registry=nacos&timestamp=1571480752218

# registryUrl
nacos://192.168.139.101:8848/org.apache.dubbo.registry.RegistryService?application=dubbo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F192.168.139.1%3A20880%2Forg.apache.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddubbo-provider%26bean.name%3Dorg.apache.dubbo.demo.DemoService%26bind.ip%3D192.168.139.1%26bind.port%3D20880%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D23412%26qos.port%3D22222%26register%3Dtrue%26release%3D%26side%3Dprovider%26timestamp%3D1571480752244&pid=23412&qos.port=22222&timestamp=1571480752218

# providerUrl
dubbo://192.168.139.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-provider&bean.name=org.apache.dubbo.demo.DemoService&bind.ip=192.168.139.1&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=23412&qos.port=22222&register=true&release=&side=provider&timestamp=1571480752244

# overrideSubscribeUrl
provider://192.168.139.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-provider&bean.name=org.apache.dubbo.demo.DemoService&bind.ip=192.168.139.1&bind.port=20880&category=configurators&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=23412&qos.port=22222&register=true&release=&side=provider&timestamp=1571480752244
  • originInvoker.URL:是 Invoker 中直接获取的 URL 信息。
  • registryUrl:将 originInvoker.URL 的 protocol 协议替换成 register 参数对应的协议,并删除 register 参数。
  • providerUrl:直接获取 export 参数。
  • overrideSubscribeUrl:将 protocol 协议替换成 provider,并新添加 category=configurators&check=false 参数。

下面先来分析 doLocalExport 方法的逻辑,如下:

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
    String key = getCacheKey(originInvoker);

    return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
        Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
        return new ExporterChangeableWrapper<>(
            // 最终调用 DubboProtocol 发布服务
            (Exporter<T>) protocol.export(invokerDelegate), originInvoker);
    });
}

总结: 最终调用具体的协议 protocol.export 发布服务,如 DubboProtocol。

posted on 2019-10-22 08:10  binarylei  阅读(416)  评论(0编辑  收藏  举报

导航