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。

Dubbo 系列(05-1)服务发布的更多相关文章

  1. Dubbo系列讲解之服务注册【3万字长文分享】 23/100 发布文章

    服务注册的几个步骤   对于RPC框架的服务注册,一般包含了如下的流程: 加载服务提供者,可能是通过xml配置的,也可能是通过扫描注解的 实例化服务提供者,并以服务接口作为key,实现类作为value ...

  2. dubbo源码之服务发布与注册

    服务端发布流程: dubbo 是基于 spring 配置来实现服务的发布的,对于dubbo 配置文件中看到的<dubbo:service>等标签都是服务发布的重要配置 ,对于这些提供可配置 ...

  3. dubbo框架揭秘之服务发布

    通常情况下是通过Spring配置的方式去实现服务的发布,为了方便调试,我就不采用Spring配置的方式. DemoService demo = new DemoServiceImpl(); Appli ...

  4. Dubbo源码学习--服务发布(ServiceBean、ServiceConfig)

    前面讲过Dubbo SPI拓展机制,通过ExtensionLoader实现可插拔加载拓展,本节将接着分析Dubbo的服务发布过程. 以源码中dubbo-demo模块作为切入口一步步走进Dubbo源码. ...

  5. Dubbo源码学习--服务发布(ProxyFactory、Invoker)

    上文分析了Dubbo服务发布的整体流程,但服务代理生成的具体细节介绍得还不是很详细.下面将会接着上文继续分析.上文介绍了服务代理生成的切入点,如下: Invoker<?> invoker ...

  6. Dubbo源码学习--服务发布(DubboProtocol、Exporter)

    在Dubbo服务发布的整体流程一文中,只是分析了服务发布的整体流程,具体的细节还没有进一步分析.本节将继续分析服务暴露的过程.在ServiceConfig中通过一句话即可暴露服务,如下: Export ...

  7. 2、Dubbo源码解析--服务发布原理(Netty服务暴露)

    一.服务发布 - 原理: 首先看Dubbo日志,截取重要部分: 1)暴露本地服务 Export dubbo service com.alibaba.dubbo.demo.DemoService to ...

  8. dubbo源码之四——服务发布二

    dubbo版本:2.5.4 2. 服务提供者暴露一个服务的详细过程 上图是服务提供者暴露服务的主过程: 首先ServiceConfig类拿到对外提供服务的实际类ref(如:HelloWorldImpl ...

  9. dubbo系列四、dubbo服务暴露过程源码解析

    一.代码准备 1.示例代码 参考dubbo系列二.dubbo+zookeeper+dubboadmin分布式服务框架搭建(windows平台) 2.简单了解下spring自定义标签 https://w ...

随机推荐

  1. python学习第二十天文件操作方法

    字符有的存储在内存,有的存储在硬盘,文件也有增删改查的操作方法,open()方法,read()方法,readline()方法,close()文件关闭,write()写的方法,seek() 指针移动方法 ...

  2. SQL 日期格式化与格式转化

    日期格式化 Select CONVERT(varchar(), GETDATE(), ): :57AM Select CONVERT(varchar(), GETDATE(), ): // Selec ...

  3. Ajax爬取百度图片

    目标网址 分析网址:http://image.baidu.com/search/index?tn=baiduimage&ipn=r&ct=201326592&cl=2& ...

  4. 自定义的最简单的可回调的线程任务CallbackableFeatureTask(模仿google的ListenableFutureTask)

    1.使该Task继承Callable,Runable import java.util.concurrent.Callable; import java.util.function.Consumer; ...

  5. vue下超级滚动条perfect-scrollbar(在特定框架里使用一款并非为该框架定制的库/插件)

    点我查看

  6. swagger集成到springBoot 项目中

    1 pom 文件加包依赖 <dependency> <groupId>io.springfox</groupId> <artifactId>spring ...

  7. weblogic启动脚本01

    DATE=`date +%Y%m%d%H%M%S` user=`whoami` logDir=/app/logs/sguap_admin #启动日志存放路径sguap是例子系统简称# logDestd ...

  8. java super与this关键字图解、java继承的三个特点

  9. java 继承的概念及案例

    package java09; //定义一个员工类 public class Employee { public void method(){ System.out.println("方法执 ...

  10. 为什么单个binlog会大于max_binlog_size设置

    查看参数设置mysql> show global variables like '%max_binlog_size%';+-----------------+------------+| Var ...