Dubbo 系列(05-1)服务发布
Dubbo 系列(05-1)服务发布
Spring Cloud Alibaba 系列目录 - Dubbo 篇
1. 背景介绍
相关文档推荐:
本章主要研究一下 Dubbo 服务暴露和服务引入的过程。Duboo 服务暴露和引用分别是从 ServiceConfig 和 ReferenceConfig 开始,代码位于 dubbo-config-api
工程中。Spring 容器中为了适配,分别扩展 了ServiceBean 和 ReferenceBean,这部分代码在 dubbo-config-spring
工程中。
本节先分析服务暴露的整个流程,代码围绕 ServiceConfig 展开。
1.1 服务暴露整体机制
注:图片来源《深入理解Apache Dubbo与实战》
从整体上看,Dubbo 服务暴露分为两个大部分:
- 将服务实例 ref 通过动态代理转换成 Invoker 实例。Invoker 是 Dubbo 的核心领域模型,它代表一个可执行实体,可能是一个本地实例,也可能是远程实例,也可能是集群实例,还可能是本地伪装实例。
- 将 Invoker 通过具体的协议(eg: Dubbo)转换成 Exporter。
2. 源码分析
在服务暴露整体机制中只介绍了服务暴露的核心逻辑,实际上在服务暴露之前还要进行参数校验,服务暴露时同时也需要进行服务注册,用于服务发现。Dubbo 服务导出大致可分为三个部分,本篇文章将会对这三个部分代码进行详细的分析。
-
前置工作,主要工作:一是配置检测;二是注册中心加载;三是组装 URL。
-
导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程。
-
向注册中心注册服务,用于服务发现。
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);
}
总结: 配置检查代码比较多。下面对配置检查的逻辑进行简单的总结,如下:
- 检测
<dubbo:service>
是否指定 ProviderConfig、ApplicationConfig 等配置,如果没有,使用全局默认的配置。 - 启动配置中心 ConfigCenter。
- 检测 ProviderConfig、ApplicationConfig 等核心配置类对象是否为空。
- 检测
<dubbo:service interface="xxx">
标签的 interface 属性合法性,不合法则抛出异常。 - 检测并处理泛化服务和普通服务类。
- 检测 local 和 stub 本地存根配置,并进行相应的处理。
- 检测 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 方法主要包含如下的逻辑:
- 遍历注册中心,分别构建 registryUrl,其中 address="N/A" 表示无注册中心,若为空则默认为 0.0.0.0
- 构建参数映射集合,也就是 map
- 构建注册中心链接列表,一个 address 可以配置多个地址
- 遍历链接列表,构建 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的步骤和第九步服务发布的逻辑。
- 如果没有设置协议,默认为 dubbo 协议发布服务。
- 设置服务 URL 的 metrics、applicationConfig、moduleConfig、providerConfig、protocolConfig 等参数信息。
appendParameters(map, application)
方法会将 JavaBean 的信息存储到 Map 中作为 URL 的参数。 - 解析指定方法的 MethodConfig。
- 解析方法的参数配置信息 ArgumentConfig。代码略。
- 解析泛化调用和普通调用。如果是普通通用,将接口中的方法用 "," 拼接后作为参数 methods 的值。
- token 参数设置。默认为随机值。
- 至此,参数的配置已经完成,可以组装服务的 URL。
- 此时,外部化配置可以覆盖本地的配置信息。
- 服务发布。代码略。
如下,服务发布最终生成的 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®ister=true&release=&side=provider×tamp=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,也就是说默认本地发布和远程同时发布这个服务。
- 本地发布服务:此时 scope != remote。调用 exportLocal 发布本地服务。
- 远程服务发布:此时 scope != local。远程发布以分两种情况:一是有注册中心;二是没有注册中心。
- 将 ref 动态代理生成 Invoker。如果有注册中心,首先要进一步配置服务 URL 的 dynamic(是否持久化)、monitor(监控地址)、proxy(动态代理方式) 参数,最后调用 PROXY_FACTORY 生成 Invoker。如果没有注册中心则直接调用 PROXY_FACTORY 生成 Invoker。
- 将 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®istry=nacos×tamp=1571479622848
# 本地发布 registryURL
dubbo://192.168.139.101:28880/org.apache.dubbo.DemoService?application=dubbo-provider&dubbo=2.0.2&pid=30192&qos.port=22222×tamp=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);
}
总结: 上面代码看起来比较复杂,主要做如下一些操作:
- 发布服务:调用 doLocalExport 导出服务。核心。
- 注册服务:调用 register 向注册中心注册服务。核心。
- 订阅 override :调用 subscribe 向注册中心进行订阅 override 数据。
- 返回 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®istry=nacos×tamp=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×tamp=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®ister=true&release=&side=provider×tamp=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®ister=true&release=&side=provider×tamp=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。