天天看点

(二)Dubbo底层实现的原理------服务的调用

Dubbo原理的实现之服务的调用

(一)大概描述一下整个服务调用的过程的准备工作和总体流程,后面一步一步分析

先说一下dubbo调用服务的原理需要的前置步骤,相当于准备工作:

  • dubbo在引用服务的时候是分两种方式的,第一种是我们正常使用的懒汉式的加载方式的,其实就是我们用@Reference注解的时候使用的就是懒汉式的加载方式
  • 第二种是当我们在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务,这种调用方式其实是一种饿汉式的加载方式,然后两者调用的开始其实就是从ReferenceBean 的 getObject 方法开始

现在调用完事之后,下一步就是决定引用那种服务,有三种可供选择的嗲用服务的方式:

  1. 第一种是引用本地 (JVM) 服务
  2. 第二是通过直连方式引用远程服务
  3. 第三是通过注册中心引用远程服务
  4. 然后最终都会生成一个Invoker对象,此时的Invoker对象已经具备调用本地或远程服务的能力了,但是不能直接暴露给用户,会对一下业务造成侵入,然后需要使用代理工厂类代理对象去调用Invoker的逻辑。

总的概括一下总的调用流程:

首先是ReferenceConfig类先去调用init()方法,然后会在init方法中调用Protocol的refer()方法生成一个Invoker对象实例,这里是消费服务的关键,然后会在init方法中通过createProxy(map)方法生成一个代理对象,然后在CreateProxy方法中进行判断是采用哪种调用方式(就是上面三种方式之一),并去调用refer()方法生成一个Invoker对象,接下来会把Invoker对象转换为客服端需要的对象(UserService),

引用服务的时序图:

(二)Dubbo底层实现的原理------服务的调用

整个调用过程的简化图:

(二)Dubbo底层实现的原理------服务的调用

(二)源码分析的流程:

1. 处理配置

  • 其实就是通过@Reference注解获取到消费者配置对象,然后主要用于检测 ConsumerConfig 实例是否存在
  • 获取接口的版本信息
  • 将 ApplicationConfig、ConsumerConfig、ReferenceConfig 等对象的字段信息添加到 map 中
  • 遍历 MethodConfig 列表 看有没有给每个方法中配置调用方法失败的时候的重试次数,这里其实就是dubbo重试的最小粒度其实是方法,然后放到构建Invoker对象的URL参数中字段的map中去
  • 获取服务消费者 ip 地址

2. 引用服务

  • 前提是需要+
  • 创建Invoker对象实例,在服务提供方,Invoker 用于调用服务提供类。在服务消费方,Invoker 用于执行远程调用。Invoker 是由 Protocol 实现类构建而来
    • 是在DubboProtocol 的 refer () 方法中通过

      DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);

      创建的,这里走的是dubbo协议,也可能是走Injvm协议,或者是别的协议
    • getClients(URL对象)里面主要就是这个方法用于获取客户端实例,实例类型为 ExchangeClient。ExchangeClient 实际上并不具备通信能力,它需要基于更底层的客户端实例进行通信,比如使用Netty的NettyClient通信、或者Mina的MinaClient 来进行通信
    • 然后是RegistryProtocol 的 refer() 方法,首先为 url 设置协议头,然后根据 url 参数加载注册中心实例。然后获取 group 配置,根据 group 配置决定 doRefer 第一个参数的类型
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    // 取 registry 参数值,并将其设置为协议头
    url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
    // 获取注册中心实例
    Registry registry = registryFactory.getRegistry(url);
    if (RegistryService.class.equals(type)) {
        return proxyFactory.getInvoker((T) registry, type, url);
    }

    // 将 url 查询字符串转为 Map
    Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
    // 获取 group 配置
    String group = qs.get(Constants.GROUP_KEY);
    if (group != null && group.length() > 0) {
        if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
                || "*".equals(group)) {
            // 通过 SPI 加载 MergeableCluster 实例,并调用 doRefer 继续执行服务引用逻辑
            return doRefer(getMergeableCluster(), registry, type, url);
        }
    }
    
    // 调用 doRefer 继续执行服务引用逻辑
    return doRefer(cluster, registry, type, url);
}
           
  • 然后是RegistryProtocol 的doRefer(Cluster cluster, Registry registry, Class type, URL url)方法,doRefer 方法创建一个 RegistryDirectory 实例,然后生成服务者消费者链接,并向注册中心进行注册。注册完毕后,紧接着订阅 providers、configurators、routers 等节点下的数据。完成订阅后,RegistryDirectory 会收到这几个节点下的子节点信息。由于一个服务可能部署在多台服务器上,这样就会在 providers 产生多个节点,这个时候就需要 Cluster 将多个服务节点合并为一个,并生成一个 Invoker
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
    // 创建 RegistryDirectory 实例
    RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
    // 设置注册中心和协议
    directory.setRegistry(registry);
    directory.setProtocol(protocol);
    Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
    // 生成服务消费者链接
    URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);

    // 注册服务消费者,在 consumers 目录下新节点
    if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
            && url.getParameter(Constants.REGISTER_KEY, true)) {
        registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
                Constants.CHECK_KEY, String.valueOf(false)));
    }

    // 订阅 providers、configurators、routers 等节点数据
    directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
            Constants.PROVIDERS_CATEGORY
                    + "," + Constants.CONFIGURATORS_CATEGORY
                    + "," + Constants.ROUTERS_CATEGORY));

    // 一个注册中心可能有多个服务提供者,因此这里需要将多个服务提供者合并为一个
    Invoker invoker = cluster.join(directory);
    ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
    return invoker;
}
           
  • 然后创建Invoker成功。
  • 创建代理,代理对象生成的入口方法为 ProxyFactory 的 getProxy,并对服务接口进行生成代理对象, Proxy 的 getProxy 方法获取 Proxy 子类,然后创建 InvokerInvocationHandler 对象,并将该对象传给 newInstance 生成 Proxy 实例
  • 这个具体是用JDK的动态代理生成还是使用JavassistProxyFactory生成,下面是具体的实现,这个invoker和interfaces其实是在上一大步创建好的Invoker中,通过下面代码块获取到接口串,然后进行遍历切分
  • Class<?>[] interfaces = null; String config = invoker.getUrl().getParameter("interfaces");

  • 然后把获取到的Invoker和Interfaces数组传给具体实现生成代理类的具体实现是JavassistProxyFactory还是JdkProxyFactory,然后都是通过下面的代码进行生成的,
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
        return Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }
           
  • 然后就已经拿到了代理生成远程的接口的实现对象类了,然后就可以直接使用了