Dubbo系列之 (六)服务订阅(3)
辅助链接
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)的更多相关文章
- Dubbo源码(六) - 服务路由
前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 今天,来聊点短的,服务路由Router,本文讲的是路由的调用路径,不讲路由的规则解析.想了解规则 ...
- Dubbo系列讲解之服务注册【3万字长文分享】 23/100 发布文章
服务注册的几个步骤 对于RPC框架的服务注册,一般包含了如下的流程: 加载服务提供者,可能是通过xml配置的,也可能是通过扫描注解的 实例化服务提供者,并以服务接口作为key,实现类作为value ...
- Dubbo系列之 (四)服务订阅(1)
辅助链接 Dubbo系列之 (一)SPI扩展 Dubbo系列之 (二)Registry注册中心-注册(1) Dubbo系列之 (三)Registry注册中心-注册(2) Dubbo系列之 (四)服务订 ...
- Dubbo系列之 (五)服务订阅(2)
辅助链接 Dubbo系列之 (一)SPI扩展 Dubbo系列之 (二)Registry注册中心-注册(1) Dubbo系列之 (三)Registry注册中心-注册(2) Dubbo系列之 (四)服务订 ...
- dubbo系列四、dubbo服务暴露过程源码解析
一.代码准备 1.示例代码 参考dubbo系列二.dubbo+zookeeper+dubboadmin分布式服务框架搭建(windows平台) 2.简单了解下spring自定义标签 https://w ...
- Dubbo 系列(05-1)服务发布
目录 Dubbo 系列(05-1)服务发布 Spring Cloud Alibaba 系列目录 - Dubbo 篇 1. 背景介绍 1.1 服务暴露整体机制 2. 源码分析 2.1 前置工作 2.2 ...
- Dubbo 系列(07-2)集群容错 - 服务路由
目录 Dubbo 系列(07-2)集群容错 - 服务路由 1. 背景介绍 1.1 继承体系 1.2 SPI 2. 源码分析 2.1 创建路由规则 2.2 RouteChain 2.3 条件路由 Dub ...
- Dubbo 系列(07-1)集群容错 - 服务字典
Dubbo 系列(07-1)集群容错 - 服务字典 [toc] Spring Cloud Alibaba 系列目录 - Dubbo 篇 1. 背景介绍 本篇文章,将开始分析 Dubbo 集群容错方面的 ...
- OSGi 系列(六)之服务的使用
OSGi 系列(六)之服务的使用 1. 为什么使用服务 降低服务提供者和服务使用者直接的耦合,这样更容易重用组件 隐藏了服务的实现细节 支持多个服务的实现.这样你可以互换这实现 2. 服务的使用 2. ...
随机推荐
- 【av68676164(p55-p58)】 Intel CPU和Linux内存管理
7.4.1 Intel CPU物理结构 https://www.cnblogs.com/megachen/p/9768115.html x86实模式 实模式 20位:1M内存空间 地址表示方式:段地址 ...
- 远程服务器的管理工具SSH
1.SSH是什么? SSH:Secure Shell 安全外壳协议 建立在应用层基础上的安全协议 可靠,专为远程登录会话和其他网络服务提供安全性的协议 有效防止远程管理过程中的信息泄露问题 SSH客户 ...
- JavaScript重定向
使用JavaScript重定向到其他网页的一些方法: location.href location.replace() location.assign() 语法: window.location.hr ...
- eclipse中启动tomcat出现错误的解决方法
前段时间跟着老师做课设,各方面调试都没有问题.近段时间想起来,看看之前写过的代码,翻着翻着就发现启动tomcat出现了错误 错误如下: 错误原因:tomcat路径配置有问题,之前可能配置好了然后由于种 ...
- 丢弃掉那些BeanUtils工具类吧,MapStruct真香!!!
在前几天的文章<为什么阿里巴巴禁止使用Apache Beanutils进行属性的copy?>中,我曾经对几款属性拷贝的工具类进行了对比. 然后在评论区有些读者反馈说MapStruct才是真 ...
- jQuery 基本用法
1)页面加载完成后开始运行do stuff when DOM is ready 中的语句! $(document).ready(function() { // do stuff when DOM is ...
- MySQL“被动”性能优化汇总!
年少不知优化苦,遇坑方知优化难. --村口王大爷 本文内容导图如下: 我之前有很多文章都在讲性能优化的问题,比如下面这些: <switch 的性能提升了 3 倍,我只用了这一招!> < ...
- 解Bug之路-dubbo流量上线时的非平滑问题
前言 笔者最近解决了一个困扰了业务系统很久的问题.这个问题只在发布时出现,每次只影响一两次调用,相较于其它的问题来说,这个问题有点不够受重视.由于种种原因,使得这个问题到了业务必须解决的程度,于是就到 ...
- Unity3D制作类似吃鸡的小地图
先看效果图: 实现的效果就是右上角的一个小地图,会随着人物的移动而移动,显示人物的方向,并且可以展示地图设定范围的其他的玩家 制作起来也很简单,不需要任何代码.主要原理就是先创建Render Text ...
- 漏洞重温之XSS(中)
漏洞重温之XSS(中) XSS挑战之旅 level8-level13 level8 第八关开局,发现button从搜索变成了友情链接,发现该页面情况跟前面不同,先右键查看代码,再进行尝试. 上测试代码 ...