dubbo源码—service reference

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端就可以找到并引用该服务,接下来就可以像使用本地服务一样使用该服务了,发起远程该过程调用。

原文地址:https://www.cnblogs.com/sunshine-2015/p/8253974.html

时间: 2024-08-28 07:17:14

dubbo源码—service reference的相关文章

dubbo源码学习(四)初始化过程细节:解析服务

初学dubbo的源码,只做尝试性的去学习,做为自己学习的一个记录,各位看官如果觉得写的有错误或理解的不对,请在留言区告诉我,互相学习.本人能力有限,有大神进入 时请指点. 前面大概介绍了一下关于学习dubbo源码的一些基本知识,今天将真正去看dubbo内部的实现过程,看dubbo的源码前我先把dubbo的用户指南和开发指指南大概的看了一遍,然后从上面找到相应的切入点去看源码,今天将介绍的是dubbo的初始化解析bean的过程.从之前使用过dubbo一些经验,加上http://dubbo.io/的

Dubbo源码分析系列-服务的发布

RPC简化类图 RPC模块核心接口和抽象实现 默认实现Dubbo协议的接口和抽象实现 服务发布过程 调用过程 上图是服务提供者暴露服务的主过程: 首先ServiceConfig类拿到对外提供服务的实际类ref(如:HelloWorldImpl),然后通过ProxyFactory类的getInvoker方法使用ref生成一个AbstractProxyInvoker实例,到这一步就完成具体服务到Invoker的转化.接下来就是Invoker转换到Exporter的过程. Dubbo处理服务暴露的关键

dubbo源码分析6-telnet方式的管理实现

dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分析4-基于netty的dubbo协议的server dubbo源码分析5-dubbo的扩展点机制 dubbo提供了telnet的方式,直接用命令查看服务信息等.怎么实现的呢. 1. 编解码器 com.alibaba.dubbo.remoting.transport.netty.NettyCodecA

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

dubbo版本:2.5.4 2. 服务提供者暴露一个服务的详细过程 上图是服务提供者暴露服务的主过程: 首先ServiceConfig类拿到对外提供服务的实际类ref(如:HelloWorldImpl),然后通过ProxyFactory类的getInvoker方法使用ref生成一个AbstractProxyInvoker实例,到这一步就完成具体服务到Invoker的转化.接下来就是Invoker转换到Exporter的过程. Dubbo处理服务暴露的关键就在Invoker转换到Exporter的

dubbo源码学习(五)dubbo暴露服务的过程

初学dubbo的源码,只做尝试性的去学习,做为自己学习的一个记录,各位看官如果觉得写的有错误或理解的不对,请在留言区告诉我,互相学习.本人能力有限,有大神进入 时请指点. dubbo采用的nio异步的通信,通信协议默认为 netty,当然也可以选择 mina,grizzy.在服务端(provider)在启动时主要是开启netty监听,在zookeeper上注册服务节点,处理消费者请求,返回处理后的消息给消费者,消费者使用服务时主要是订阅服务的节点,监听zookeeper节点目录,服务端的变化时z

Dubbo 源码分析 - 服务导出全过程解析

1.服务导出过程 本篇文章,我们来研究一下 Dubbo 导出服务的过程.Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑.整个逻辑大致可分为三个部分,第一是前置工作,主要用于检查参数,组装 URL.第二是导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程.第三是向注册中心注册服务,用于服务发现.本篇文章将会对这三个部分代码进行详细的分析,在分析之前,我们先来了解一下服务的导出过程. Dubbo 支持两种服务导出方式,

【Dubbo源码阅读系列】服务暴露之远程暴露

引言 什么叫 远程暴露 ?试着想象着这么一种场景:假设我们新增了一台服务器 A,专门用于发送短信提示给指定用户.那么问题来了,我们的 Message 服务上线之后,应该如何告知调用方服务器,服务器 A 提供了 Message 功能?那么我们是不是可以把目前已提供的服务暴露在一个地方,让调用方知道某台机器提供了某个特定功能?带着这样的假设,我们今天就来聊聊 Dubbo 服务暴露之远程暴露!! 服务远程暴露 先回顾一下上篇文章,上篇文章我们聊到了 ServiceConfig 的 export() 方

【Dubbo源码阅读系列】之远程服务调用(上)

今天打算来讲一讲 Dubbo 服务远程调用.笔者在开始看 Dubbo 远程服务相关源码的时候,看的有点迷糊.后来慢慢明白 Dubbo 远程服务的调用的本质就是动态代理模式的一种实现.本地消费者无须知道远程服务具体的实现,消费者和提供者通过代理类来进行交互!! 一.JAVA 动态代理 简单看一段代码回顾一下动态代理: public class MyInvocationHandler implements InvocationHandler{ private Object object; publi

Dubbo源码解析之SPI(一):扩展类的加载过程

Dubbo是一款开源的.高性能且轻量级的Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用.智能容错和负载均衡,以及服务自动注册和发现. Dubbo最早是阿里公司内部的RPC框架,于 2011 年开源,之后迅速成为国内该类开源项目的佼佼者,2018年2月,通过投票正式成为 Apache基金会孵化项目.目前宜信公司内部也有不少项目在使用Dubbo. 本系列文章通过拆解Dubbo源码,帮助大家了解Dubbo,做到知其然,并且知其所以然. 一.JDK SPI 1.1 什么是SPI? S