解 Dubbo 服务引用

281 阅读4分钟

dubbo 服务引用过程

dubbo 的使用过程中消费者端会依赖服务端提供的 api 包(接口 jar 包) , 这些 api 包中只含有服务的 Interface 的 class 文件 , 在进行服务调用的时候使用 Interface 的一个引用,就可以进行远程的调用了,因为 dubbo 在客户端动态的生成了一个该 Interface 类型的代理类。在这个代理类中封装了远程服务调用的组件。获取这个 Interface 类型的代理对象的实例通过 spring IOC 容器 ,自动注入的方式或者通过 getBean 的方式。服务引用的入口代码:

com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
com.alibaba.dubbo.config.spring.schema.DubboBeanDefinitionParser
com.alibaba.dubbo.config.spring.ReferenceBean

ReferenceBean 实现了 spring 的 FactoryBean 接口 , 该接口有一个 T getObject() 函数 ,dubbo 服务引用的开端就是在实现的这个 getObject() 函数中。当对任意服务 Interface 进行自动注入或者 getBean 获取时,就会触发服务的引用过程。

服务引用分为和服务提供者在同一个 JVM 实例中和在不同的 JVM 引用,和服务提供者在同一 JVM 实例中引用不会打开网络连接,同一 JVM 实例内调用不需要经过网络传输过程。在和服务提供者不同的 JVM 实例内引用时就必须打开网络连接,否则无法调用远程服务。

和服务提供者在不同 JVM 实例中引用,首先消费者要去注册中心进行注册, 接下来订阅所引用的服务。当服务提供者接收到消费者的订阅消息后会去注册中心查找已经注册的所有服务 , 然后匹配哪一个服务是当前消费者正在订阅的服务 , 如果匹配成功了就会通过注册中心通知消费者找到了消费者订阅的服务,消费者接收到注册中心发送的匹配服务成功消息后 , 会调用消费者注册的订阅通知监听 (NotifyListener), NotifyListener 中的 notify 会被调用 。 dubbo 提供了一个 RegistryDirectory 实现了 NotifyListener 接口 , 在 notify 函数中去刷新 Invoker (refreshInvoker) 。这时候会去创建一个新的 Invoker , 创建的这个 Invoker 还是和之前博客中讨论过的创建 Invoker 的过程一样, Invoker 会被 Filter 、 Listener 包装。在创建这个 Invoker 的过程中也会打开网络连接。

这里使用的注册中心是 multicast 。 zookeeper , redis 注册中心的具体实现方式不同。如果在消费者服务引用的过程中,服务提供者没有正常启动的话,是不会去进行打开网络连接操作的,如果在消费者连接提供者的过程中提供者挂掉了抛出异常 RemotingException 。

打开网络连接

消费者在引用服务的过程中如果和提供者不在同一 JVM 实例内,需要去打开网络连接,来和服务提供者进行网络通信。打开网络连接的过程和服务端在网络中暴露服务的顺序一致 Transporter , Exchanger , ExchangeClient 可选的实现有 NettyClient (默认) , MinaClient , GrizzlyClient 。消费者建立客户端打开网络连接之后要和所订阅的服务端建立连接。Interface 引用 , Invoker , ExchangeClient , Provider 之间的关系大致是这样:

每一个服务引用都可能对应多个 Invoker , 每一个 Invoker 指向了一个 Provider ,消费者内部会开启多个客户端与提供者开启的一个服务端进行通信。当使用服务引用调用服务时,就可以在多个 Invoker 之间进行负载均衡。如果每个 Invoker 只有一个 Client,这样称为共享连接。每一个服务引用也可以使用多个网络连接,在 <dubbo:reference connections="3" /> 进行配置 , 默认使用共享网络连接。

创建服务存根接口的代理对象

通过 ProxyFactory 扩展点 getProxy 来获取一个代理对象的实例 ,dubbo 默认使用的是 JavassistProxyFactory 扩展 , 动态的生成一个代理类和这个代理类的实例对象 。我的 Interface 代码 :

package net.j4love.dubbo.quickstart.provider;

public interface DemoService {

    String echoWithCurrentTime(String src);
}

生成的接口代理类代码 :

package com.alibaba.dubbo.common.bytecode;

public class com.alibaba.dubbo.common.bytecode.proxy0
implements interface net.j4love.dubbo.quickstart.provider.DemoService ,
    interface com.alibaba.dubbo.rpc.service.EchoService {

    // 存储的是所有实现的接口中的 method
    public static java.lang.reflect.Method[] methods;

    private java.lang.reflect.InvocationHandler handler;

    public proxy0(java.lang.reflect.InvocationHandler $1) {
        handler=$1;
    }

    public java.lang.String echoWithCurrentTime(java.lang.String $1) {
        Object[] args = new Object[1];
        args[0] = ($w)$1;
        Object ret = handler.invoke(this, methods[0], args);
        return (java.lang.String)ret;
    }

    public java.lang.Object $echo(java.lang.Object $1) {
        Object[] args = new Object[1];
        args[0] = ($w)$1;
        Object ret = handler.invoke(this, methods[1], args);
        return (java.lang.Object)ret;
    }
}

生成的 com.alibaba.dubbo.common.bytecode.Proxy 的实现类代码 :

public class com.alibaba.dubbo.common.bytecode.Proxy0 extends
com.alibaba.dubbo.common.bytecode.Proxy {
    public Proxy0() {}

    public Object newInstance(java.lang.reflect.InvocationHandler h) {
        return new com.alibaba.dubbo.common.bytecode.proxy0($1);
    }
}

数字 0 是自增生成的 , 从 0 开始自增。

回声测试

所有的动态生成的引用接口代理类都实现了 com.alibaba.dubbo.rpc.service.EchoService 这个接口,获取到的引用可以强制转换成 EchoService 调用 $echo 函数来测试服务是否可用。

泛化调用

泛化调用是用 interface com.alibaba.dubbo.rpc.service.GenericService 接口来替代自定义的服务接口,通过 $invoke 函数来调用远程服务 , 这种方式适用于消费者端本地没有服务提供者端的接口 jar 包时,但是却知道接口的全名称 , 接口全名称是必须知道的因为要靠这个名称去注册中心寻找是否有注册的服务提供者。泛化调用只需要进行配置就可使用 , generic 设置为 true:

<dubbo:reference id="demoService"
                     version="1.0.0"
                     generic="true"
                     interface="net.j4love.dubbo.quickstart.provider.DemoService" />

看完本文有收获?请分享给更多人

微信关注「黑帽子技术」加星标,看精选 IT 技术文章