辅助链接

Dubbo系列之 (一)SPI扩展

Dubbo系列之 (二)Registry注册中心-注册(1)

Dubbo系列之 (三)Registry注册中心-注册(2)

Dubbo系列之 (四)服务订阅(1)

Dubbo系列之 (五)服务订阅(2)

Dubbo系列之 (六)服务订阅(3)

RegistryDirectory

当RegistryDirectory#substribe()方法被RegistryProtocol#refer()方法调用时,本地服务消费端会与注册中心交互,拉取最新的服务提供者,并与这些服务提供者建立TCP连接。

  public void subscribe(URL url) {
setConsumerUrl(url);
CONSUMER_CONFIGURATION_LISTENER.addNotifyListener(this);
serviceConfigurationListener = new ReferenceConfigurationListener(this, url);
registry.subscribe(url, this);
}

从上面的代码块可以知道RegistryDirectory直接调用的注册中心的substribe()方法。我们以ZookeeperRegistry为例,查看其方法doSubscribe()。

  public void doSubscribe(final URL url, final NotifyListener listener) {
......
} else {
// 正常服务订阅
List<URL> urls = new ArrayList<>();
for (String path : toCategoriesPath(url)) {
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap<>());
//监听,当目录变更时,调用notify方法
ChildListener zkListener = listeners.computeIfAbsent(listener, k -> (parentPath, currentChilds) -> ZookeeperRegistry.this.notify(url, k, toUrlsWithEmpty(url, parentPath, currentChilds)));
zkClient.create(path, false);
List<String> children = zkClient.addChildListener(path, zkListener);
if (children != null) {
urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
//订阅节点后,要拉取节点的最新数据
notify(url, listener, urls);
}
} catch (Throwable e) {
throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}

从上面代码,可以知道就是监听zookeeper上的providers,routers,configurations节点,并注册其监听器。当订阅完这些节点后,需要重新拉取最新的提供者数据,即调用其notify()方法。

notify方法的作用

notify()方法最终会调用RegistryDirectory的notify()方法。该方法的主要完成如下内容:

1、得到zookeeper的configurations节点下的URLS,并转化为configurators

2、得到zookeeper的routers节点下的URLS,并转化为Routers

3、激活3.x的AddressListener特性

4、得到zookeeper的providers节点下的URLS,与其服务提供者创建TCP链接,把URL转化为 invoker

我们主要来看下第四点,其方法为refreshOverrideAndInvoker()。

 private void refreshOverrideAndInvoker(List<URL> urls) {
// mock zookeeper://xxx?mock=return null
overrideDirectoryUrl();
refreshInvoker(urls);
}

该方法主要是2个操作,1个是如果必要的话,重新覆盖订阅的URL,因为dubbo的服务调用URL的一些配置,比如路由,mock可以在monitor中心进行动态修改。所以需要重新覆盖本地的URL一些参数。2、是通过refreshInvoker()与服务端建立TCP链接。

/**
*
* 把提供者者的URL List 转化为 Invoker Map结合,转化规则如下:
* Convert the invokerURL list to the Invoker Map. The rules of the conversion are as follows:
* <ol>
*
* <li> If URL has been converted to invoker, it is no longer re-referenced and obtained directly from the cache,
* and notice that any parameter changes in the URL will be re-referenced.</li>
* 如果URL已经在缓存中,则不用重新引用该服务提供者(即重新建立TCP连接),如果URL的参数变更需要重新引用。
*
*
* <li>If the incoming invoker list is not empty, it means that it is the latest invoker list.</li>
* 如果传入的调用程序列表不是空的,这意味着它是最新的调用程序列表
* * <li>If the list of incoming invokerUrl is empty, It means that the rule is only a override rule or a route
* rule, which needs to be re-contrasted to decide whether to re-reference.</li>
* </ol>
* 如果传入的invokerUrl列表为空,则意味着该规则只是一个覆盖规则或路由规则,需要对其进行重新对比以决定是否重新引用
*
* @param invokerUrls this parameter can't be null
*/
// TODO: 2017/8/31 FIXME The thread pool should be used to refresh the address, otherwise the task may be accumulated.
private void refreshInvoker(List<URL> invokerUrls) {
Assert.notNull(invokerUrls, "invokerUrls should not be null"); //如果只有一个提供者,且为空协议,则禁止链接和销毁invoker
if (invokerUrls.size() == 1
&& invokerUrls.get(0) != null
&& EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) { this.forbidden = true; // Forbid to access
this.invokers = Collections.emptyList();
routerChain.setInvokers(this.invokers);
destroyAllInvokers(); // Close all invokers
} else {
this.forbidden = false; // Allow to access
Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
if (invokerUrls == Collections.<URL>emptyList()) {
invokerUrls = new ArrayList<>();
}
if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {
invokerUrls.addAll(this.cachedInvokerUrls);
} else {
this.cachedInvokerUrls = new HashSet<>();
this.cachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison
}
if (invokerUrls.isEmpty()) {
return;
}
Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map /**
* If the calculation is wrong, it is not processed.
*
* 1. The protocol configured by the client is inconsistent with the protocol of the server.
* eg: consumer protocol = dubbo, provider only has other protocol services(rest).
* 2. The registration center is not robust and pushes illegal specification data.
*
*/
if (CollectionUtils.isEmptyMap(newUrlInvokerMap)) {
logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls
.toString()));
return;
} List<Invoker<T>> newInvokers = Collections.unmodifiableList(new ArrayList<>(newUrlInvokerMap.values()));
// pre-route and build cache, notice that route cache should build on original Invoker list.
// toMergeMethodInvokerMap() will wrap some invokers having different groups, those wrapped invokers not should be routed.
routerChain.setInvokers(newInvokers);
this.invokers = multiGroup ? toMergeInvokerList(newInvokers) : newInvokers;
this.urlInvokerMap = newUrlInvokerMap; try {
destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
} catch (Exception e) {
logger.warn("destroyUnusedInvokers error. ", e);
}
}
}
 private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<>();
if (urls == null || urls.isEmpty()) {
return newUrlInvokerMap;
}
Set<String> keys = new HashSet<>();
//目前的协议
String queryProtocols = this.queryMap.get(PROTOCOL_KEY); //服务提供方URLproviderUrl ,看这些提供方是否支持目前协议
for (URL providerUrl : urls) {
// If protocol is configured at the reference side, only the matching protocol is selected
if (queryProtocols != null && queryProtocols.length() > 0) {
boolean accept = false;
String[] acceptProtocols = queryProtocols.split(",");
for (String acceptProtocol : acceptProtocols) {
if (providerUrl.getProtocol().equals(acceptProtocol)) {
accept = true;
break;
}
}
if (!accept) {
continue;
}
} //空协议过滤
if (EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) {
continue;
} //没有在Spi框架找不大的扩展点,过滤
if (!ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) {
logger.error(new IllegalStateException("Unsupported protocol " + providerUrl.getProtocol() +
" in notified url: " + providerUrl + " from registry " + getUrl().getAddress() +
" to consumer " + NetUtils.getLocalHost() + ", supported protocol: " +
ExtensionLoader.getExtensionLoader(Protocol.class).getSupportedExtensions()));
continue;
} // 合并url,一般参数配置可能配置在消费端,提供端,需要进行合并。合并规则为:override(配置中心) > -D(启动运行指定) >Consumer(消费端) > Provider(提供方)
URL url = mergeUrl(providerUrl); String key = url.toFullString(); // The parameter urls are sorted
if (keys.contains(key)) { // Repeated url
continue;
}
keys.add(key);
// Cache key is url that does not merge with consumer side parameters, regardless of how the consumer combines parameters, if the server url changes, then refer again
/**
*key:是没有合并消费端端配置参数的Url(provider端),
* 缓存键是不与用户端参数合并的url,无论用户如何合并参数,如果服务器url更改,则再次引用
*
*
*/
Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);
if (invoker == null) {// 看本地缓存是否存在,如果存在// Not in the cache, refer again
try {
boolean enabled = true;
if (url.hasParameter(DISABLED_KEY)) {//是否disable
enabled = !url.getParameter(DISABLED_KEY, false);
} else {
enabled = url.getParameter(ENABLED_KEY, true);
}
if (enabled) {
/**
* 把rpc invoker 、mergeUrl(override > -D >Consumer > Provider 参数内容),原providerUrl
* url: getProtocol()=dubbo
*/
invoker = new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl);
}
} catch (Throwable t) {
logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);
}
if (invoker != null) { // Put new invoker in cache
newUrlInvokerMap.put(key, invoker);
}
} else {
newUrlInvokerMap.put(key, invoker);
}
}
keys.clear();
return newUrlInvokerMap;
}

上面的注释已经非常清楚,不在详细讲解。最后所以提供者的URL,会被转化为InvokerDelegate。该类代表一个Invoker对象的委派类,里面包括真实的Invoker和相应的提供者的URL。并把这些InvokerDelegate放入到newUrlInvokerMap成员变量上。

Protocol.refer()的博大精深

来看下如下这条语句:

invoker = new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl);

又是通过protocol.refer(serviceType, url)获取一个Invoker,此时URL的getProtocol()==dubbo,所以会调用DubboProtocol#refer()方法。而DubboProtocol的refer()还是AbstractProtocol#refer()。

   public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
/**
* 异步转同步Invoker
*/
return new AsyncToSyncInvoker<>(protocolBindingRefer(type, url));
}

即返回一个异步转同步的AsyncToSyncInvoker。DubboProtocol实现模板方法protocolBindingRefer()。

   public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
// 优化序列化内容,目前没什么内容
optimizeSerialization(url); // create rpc invoker.
/**
*
* 创建RPC DubboInvoker
*/
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker); return invoker;
}

该方法主要创建了DubboInvoker对象,并放入invokers中,通过getClients()方法得到具体的TCP连接客户端ExchangeClient。

总结

从上面的分析可以知道,服务订阅的过程,服务拉取的方式是通过通知这种方式来获取,并且知道了Invoker的具体一个实现DubboInvoker和TCP连接客户端ExchangeClient进行关联的,在下一章我们将剖析ExchangeClient是如何实现的。

Dubbo系列之 (六)服务订阅(3)的更多相关文章

  1. Dubbo源码(六) - 服务路由

    前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 今天,来聊点短的,服务路由Router,本文讲的是路由的调用路径,不讲路由的规则解析.想了解规则 ...

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

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

  3. Dubbo系列之 (四)服务订阅(1)

    辅助链接 Dubbo系列之 (一)SPI扩展 Dubbo系列之 (二)Registry注册中心-注册(1) Dubbo系列之 (三)Registry注册中心-注册(2) Dubbo系列之 (四)服务订 ...

  4. Dubbo系列之 (五)服务订阅(2)

    辅助链接 Dubbo系列之 (一)SPI扩展 Dubbo系列之 (二)Registry注册中心-注册(1) Dubbo系列之 (三)Registry注册中心-注册(2) Dubbo系列之 (四)服务订 ...

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

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

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

    目录 Dubbo 系列(05-1)服务发布 Spring Cloud Alibaba 系列目录 - Dubbo 篇 1. 背景介绍 1.1 服务暴露整体机制 2. 源码分析 2.1 前置工作 2.2 ...

  7. Dubbo 系列(07-2)集群容错 - 服务路由

    目录 Dubbo 系列(07-2)集群容错 - 服务路由 1. 背景介绍 1.1 继承体系 1.2 SPI 2. 源码分析 2.1 创建路由规则 2.2 RouteChain 2.3 条件路由 Dub ...

  8. Dubbo 系列(07-1)集群容错 - 服务字典

    Dubbo 系列(07-1)集群容错 - 服务字典 [toc] Spring Cloud Alibaba 系列目录 - Dubbo 篇 1. 背景介绍 本篇文章,将开始分析 Dubbo 集群容错方面的 ...

  9. OSGi 系列(六)之服务的使用

    OSGi 系列(六)之服务的使用 1. 为什么使用服务 降低服务提供者和服务使用者直接的耦合,这样更容易重用组件 隐藏了服务的实现细节 支持多个服务的实现.这样你可以互换这实现 2. 服务的使用 2. ...

随机推荐

  1. Django REST framework 单元测试

    Django REST framework 单元测试 只是简单记录一下测试代码怎么写 环境 Win10 Python3.7 Django2.2 项目 参照官网 快速开始 写了一个 demo 测试 参照 ...

  2. java_字节流、字符流的使用方法

    字节流 字节输出流[OutputStream] java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地.它定义了字节输出流的基本共性功能方法. p ...

  3. ECS7天实践进阶训练营Day2:基于阿里云ECS部署MediaWiki

    一.概述 MediaWiki是全球最著名的开源Wiki程序,运行于PHP+MySQL环境,MediaWiki从2002年被作为维基百科的系统软件,并由大量其他应用实例(例如萌娘百科),因此MediaW ...

  4. 基于Asp.net Core 3.1实现的Redis及MemoryCache缓存助手CacheHelper

    这几天在面试,这个关于Redis缓存的博客一直没空写,今天总算有点时间了. 从很久很久之前,我就一直想学Redis了,反正看到各大招聘网上都要求Redis,不学就太落后了. 一开始我是按微软官网文档那 ...

  5. C#开发笔记,点点细微,处处真情,记录开发中的难言之隐

    该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/956 访问. 概述 本系列文章将会向大家介绍本人实际开发过程中所遇到技术 ...

  6. Mybatis——ResultMap(结果集映射)的使用

    ResultMap的使用 在Mybatis中,可以使用resultMap(结果集映射)作为sql的返回类型 一般用来解决如下问题: 数据库表字段名和实体类属性名不一致的问题: 多对一问题: 例如:多个 ...

  7. JS实例-01

    输入成绩(0-100),不同的分数段奖励不同while(true){var a=prompt('请输入成绩');if (a>=0&&a<=100){ break;}}if ...

  8. importTSV工具导入数据到hbase

    1.建立目标表test,确定好列族信息. create'test','info','address' 2.建立文件编写要导入的数据并上传到hdfs上 touch a.csv vi a.csv 数据内容 ...

  9. 搭建vue开发环境的步骤,六步完成

    搭建vue开发环境的步骤,其实也挺简单的,之前这环境的配置也困扰着我一:在搭建vue的开发环境之前,一定一定要先下载node.js,vue的运行是要依赖于node的npm的管理工具来实现,下载地址:h ...

  10. PythonCrashCourse 第四章习题

    Python 从入门到实践第四章习题 4.1想出至少三种你喜欢的比萨,将其名称存储在一个列表中,再使用for 循环将每种比萨的名称都打印出来 修改这个for 循环,使其打印包含比萨名称的句子,而不仅仅 ...