service reference

在编写好服务之后,dubbo会将服务export出去,这个时候就可以编写consumer来调用这个服务了。dubbo作为一个rpc框架,使用者使用远程服务和使用本地服务是类似的,不用关心远程服务在哪里,怎么引用的,因为dubbo包含了自动发现和引用服务的功能。

dubbo引用服务主要工作:

  • 创建proxy和Invoker(DubboInvoker里面会启动NettyClient)
  • 将consumer注册到注册中心
  • 订阅configurators、providers、routers

通过Java代码引用

ReferenceConfig<TestDubboService> reference = new ReferenceConfig();
reference.setApplication(new ApplicationConfig(appName));
reference.setRegistry(new RegistryConfig(dubboRegistry));
reference.setInterface(TestDubboService.class);
reference.setTimeout(timeout);
TestDubboService service = (TestDubboService)reference.get();

使用Java代码配置很明显,直接使用ReferenceConfig.get获取一个proxy

通过spring配置引用

<dubbo:reference interface="com.test.service.TestDubboService" id="testDubboService" />

spring解析该dubbo自定义标签的时候(请提前学习spring如何解析自定义标签),会初始化ReferenceBean,该bean是一个factoryBean并且继承自ReferenceConfig,在getBean方法中调用了ReferenceConfig.get,接下来的方式就和上面“使用Java代码引用”一致了。所以dubbo引用服务的工作就主要在于如何创建proxy。

ReferenceConfig初始化

ReferenceConfig的主要作用是配置并引用远程服务,创建远程服务的本地代理。ReferenceBean继承自ReferenceConfig,ReferenceConfig是一个FactoryBean ,实现了getObject方法,在spring容器初始化完成的时候会初始化配置为非lazyInit的bean,也就会调用ReferenceBean.getObject方法,里面会调用ReferenceConfig.get方法,从而触发ServiceConfig的初始化方法ServiceConfig.init。

inti方法的主要逻辑是:

  1. 判断是否已经初始化,get方法是同步方法,所以只需直接判断标志位initialized即可
  2. 判断配置的interface是否正确
  3. 判断-D参数配置或者配置文件是否配置是直连提供者
  4. 配置application、module、registries、monitor等
  5. 检查stub和mock配置(类似provider端的检查)
  6. 搜集需要配置到URL中的参数,先将参数收集到map中,URL参数在refer的过程中极其重要,dubbo中的所有配置几乎都是靠URL传递,从URL中获取或者设置到URL中
  7. 创建远程服务的本地代理,createProxy

如何创建proxy

由于是远程服务,consumer需要有一个代理来处理consumer发起的远程过程调用。dubbo通过远程调用的可执行体Invoker的代理来实现。接下来主要就是先创建Invoker,然后创建Invoker的proxy。

创建Invoker调用堆栈如下

createProxy的主要功能:

  1. 判断ReferenceConfig中是否配置了url,如果配置了url,则不从registry中获取,直接使用配置的url。
  2. 没有配置url,从registry配置拼装url
  3. 根据上面配置好的URL来refer对应的服务,创建远程服务的可执行体Invoker
  4. 将所有的invokers聚合成一个可执行实体MockClusterInvoker
  5. 给MockClusterInvoker创建一个代理类,这个代理类就是我们在consumer端使用的远程服务代理,该代理实现了对应的service接口,对应的InvocationHandler就是作为代理类构造方法入参的MockClusterInvoker,在后面一节分析中会说明consumer怎么发起调用

下面主要说说Invoker的创建过程:

  1. 和provider一样,会调用ProtocolFilterWrapper#refer和ProtocolListenerWrapper#refer,分别构造filter链和调用对应的listener.referred
  2. RegistryProtocol#refer会判断是否配置了group
  3. 根据上面的调用堆栈会调用ZookeeperRegistry#doSubscribe,该方法中会订阅providers、configurators等,并通过notify方法来调用到AbstractRegistry#notify,里面会针对每一个category调用对应listener.notify,consumer端的listener是RegistryDirectory,所以这里会调用RegistryDirectory#notify
  4. RegistryDirectory#notify,这个方法也是registry对应节点变化后监听的listener,会对每一种监听的节点类型做处理,这里先只看provider的处理,调用refreshInvoker方法
  5. refreshInvoker方法就是将配置好的url转换为Invoker,如果转化后的invoker至少有一个,并且少于原来的invoker(缓存的invoker),则会把废弃的invoker销毁掉(destroy)

这里具体说明一下consumer订阅,ZookeeperRegistry#doSubscribe中会将url中配置的category取出来拼接成registry的目录节点形式,然后订阅这些节点

// RegistryProtocol类
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
// ... 省略中间代码
// 这里调用的是RegistryDirectory.subscribe方法
// 在这里将consumer端想要订阅的category添加到url,包括providers,configurators,routers
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));
return cluster.join(directory);
} // ZookeeperRegistry在doSubscribe调用自己的下面这个方法,将URL中的category转化为registry中的目录对应的url
private String[] toCategoriesPath(URL url) {
String[] categroies;
if (Constants.ANY_VALUE.equals(url.getParameter(Constants.CATEGORY_KEY))) {
// 如果配置的category是*,则取所有的category:providers,consumer,routers,configurators
categroies = new String[] {Constants.PROVIDERS_CATEGORY, Constants.CONSUMERS_CATEGORY,
Constants.ROUTERS_CATEGORY, Constants.CONFIGURATORS_CATEGORY};
} else {
categroies = url.getParameter(Constants.CATEGORY_KEY, new String[] {Constants.DEFAULT_CATEGORY});
}
String[] paths = new String[categroies.length];
for (int i = 0; i < categroies.length; i ++) {
// 将category拼接成registry中的目录形式,类似:/dubbo/com.test.service.TestDubboService/providers
paths[i] = toServicePath(url) + Constants.PATH_SEPARATOR + categroies[i];
}
return paths;
} protected void doSubscribe(final URL url, final NotifyListener listener) {
try {
if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
// ... 省略中间代码
} else {
List<URL> urls = new ArrayList<URL>();
for (String path : toCategoriesPath(url)) {
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
if (listeners == null) {
// 如果之前该路径没有添加过listener,则创建一个map来放置listener
zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
listeners = zkListeners.get(url);
}
ChildListener zkListener = listeners.get(listener);
if (zkListener == null) {
// 如果没有添加过对于子节点的listener,则创建
listeners.putIfAbsent(listener, new ChildListener() {
public void childChanged(String parentPath, List<String> currentChilds) {
ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
}
});
zkListener = listeners.get(listener);
}
zkClient.create(path, false);
// 添加listener到该目录及其子节点
List<String> children = zkClient.addChildListener(path, zkListener);
if (children != null) {
urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
// 这个方法本身会导致监听的目录及其子节点变化,直接调用notify
notify(url, listener, urls);
}
} catch (Throwable e) {
throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}

上面是consumer的订阅部分的源码,在consumer订阅的时候会调用FailbackRegistry#notify,接下来就是将url转换为Invoker,接下来的调用链路可以参考上面方法调用堆栈的图,转化的主要代码为:

com.alibaba.dubbo.registry.integration.RegistryDirectory#refreshInvoker
com.alibaba.dubbo.registry.integration.RegistryDirectory#toInvokers

这两个方法中的源代码注释较为详细了就不再赘述了。

在toInvokers方法中会调用DubboProtocol#refer,在该方法中启动NettyClient。

总结

provider提供服务后,consumer端就可以找到并引用该服务,接下来就可以像使用本地服务一样使用该服务了,发起远程该过程调用。

dubbo源码—service reference的更多相关文章

  1. dubbo源码—Service Invoke

    dubbo的远程调用过程是怎么样的? dubbo远程过程调用经过了那些处理? 发起远程调用的时候究竟传了什么数据给provider? 要解决这些问题,欢迎一起探讨走进dubbo源码栏目. 在servi ...

  2. dubbo源码—Service Reply

    dubbo通过netty将请求发送到provider的时候,provider之前已经启动好的NettyServer监听指定端口的时候会收到来自consumer的请求,将通过网络发送来的二进制编码成Re ...

  3. dubbo源码—service export

    在应用编写好服务并进行之后,dubbo负责将服务export出去,dubbo export服务的时候主要做了以下几件事: 将服务export到本地(根据scope的配置) 创建Invoker(启动本地 ...

  4. dubbo源码分析2-reference bean发起服务方法调用

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  5. dubbo源码分析3-service bean的创建与发布

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  6. Dubbo源码-从HelloWorld开始

    Dubbo简介 Dubbo,相信做后端的同学应该都用过,或者有所耳闻.没错,我就是那个有所耳闻中的一员. 公司在好几年前实现了一套自己的RPC框架,所以也就没有机会使用市面上琳琅满目的RPC框架产品. ...

  7. Dubbo源码-Dubbo是如何随心所欲自定义XML标签的

    叨叨 今天考虑了很久要不要写这篇文章. 距离<Dubbo源码>系列的开篇到现在已经快两个月时间了.当时是想着工作上的RPC框架使用存在一些让人头疼的问题,就来看看Dubbo给出了一套什么样 ...

  8. dubbo源码研究(一)

    1. dubbo源码研究(一) 1.1. dubbo启动加载过程 我们知道,现在流行注解方式,用spring管理服务,dubbo最常用的就是@Reference和@Service了,那么我首先找到这两 ...

  9. dubbo源码解析五 --- 集群容错架构设计与原理分析

    欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 博客园 Dubbo 入门之二 --- 项目结构解析 博客园 Dubbo 源码分析系列之 ...

随机推荐

  1. Git~GitLab当它是一个CI工具时

    CI我们都知道,它是持续集成的意思,主要可以自动处理包括编译,测试,发布等一系列的过程,而GitLab也同样包含了这些功能,我们可以通过pipeline很容易的实现一个软件从编译,测试,发布的自动化过 ...

  2. 转自知乎-wifi破解

    上点python有意思的代码. 环境准备 python2.7 pywifi模块 字典 清除系统中的任何wifi连接记录 导入模块 这里用的模块就这三个 pywifi的_wifiutil_linux.p ...

  3. Android数据加密概述及多种加密方式 聊天记录及账户加密 提供高质量的数据保护

    Android数据加密概述及多种加密方式 聊天记录及账户加密 提供高质量的数据保护 数据加密又称password学,它是一门历史悠久的技术,指通过加密算法和加密密钥将明文转变为密文.而解密则是通过解密 ...

  4. Selenium_WebDriver登录模拟鼠标移动切换窗体等操作练习(cssSelector初练手)_Java

    cssSelector 据说cssSelector比xpath快. 所以,有固定ID属性的页面元素用By.id或者By.cssSelector("#id属性值")来找,有class ...

  5. SQL或HQL预编译语句,可以防止SQL注入,可是不能处理%和_特殊字符

    近期项目在做整改,将全部DAO层的直接拼接SQL字符串的代码,转换成使用预编译语句的方式.个人通过写dao层的单元測试,有下面几点收获. dao层代码例如以下 //使用了预编译sql public L ...

  6. JavaScript面向对象编程(9)高速构建继承关系之整合原型链

    前面我们铺垫了非常多细节.是为了让大家更加明晰prototype的使用细节: 如今能够将前面的知识整合起来,写一个函数用于高速构建基于原型链的继承关系了: function extend(Child, ...

  7. 在PowerShell脚本中集成Microsoft Graph

    作者:陈希章 发表于2017年4月23日 我旗帜鲜明地表态,我很喜欢PowerShell,相比较于此前的Cmd Shell,它有一些重大的创新,例如基于.NET的类型系统,以及管道.模块的概念等等.那 ...

  8. Iframe简单介绍(一)

    Iframe可以用在以下几个场景 1.典型系统结构,左侧是功能树,右侧就是一些常见table或者表单之类的.为了每一个功能,单独分离出来,采用iframe. 2.AJAX上传文件 3.加载别的网站内容 ...

  9. More DETAILS! PBR的下一个发展在哪里?

    最近几年图形学社区对PBR的关注非常高,也许是由于Disney以及一些游戏引擎大厂的助推,也许是因为它可以被轻松集成进实时渲染的游戏引擎当中,也许是因为许多人发现现在只需要调几个参数就能实现具有非常精 ...

  10. 【二十一】基于mysqli的表格数据练习

    mysqlitest.php <?php //调用数据库的函数 function connetionsql(){ $conn=mysqli_connect("127.0.0.1&quo ...