DUBBO用法总结十二——服务引用

"2021SC@SDUSC"

如果没有Rpc框架,我们需要网络编程代码调用远程服务,同时对于请求参数和响应结果需要手动进行序列化和反序列化。这些过程对于没有接触过网络编程的程序员来说有一定的难度。Dubbo服务引用的目的就是将远程调用的网络编程隐藏在框架当中,让程序员像调用本地服务一样调用远程服务。Dubbo提供的功能不仅如此,同时提供了服务发现,集群容错,负载均衡,服务降级等功能。

如下图是dubbo服务引用的整体流程,在引用服务时,dubbo首先会从注册中心获取注册服务的url信息,并利用protocol将url信息转换为Invoker,以此屏蔽网络调用的流程。经过第一个步骤之后,我们获取到了invoker集合,dubbo使用集群策略和负载均衡将invoker集合转换为一个invoker对外服务。现在的Invoker已经能够实现远程调用,但是使用起来不方便,Dubbo使用ProxyFactory将Invoker转换为用户所需要的接口。

二、Dubbo服务引用

在Dubbo中提供者负责服务的导出和发布,而消费着负责订阅服务和服务的导入。在 Dubbo 中,我们可以通过两种方式引用远程服务。第一种是使用服务直连的方式引用服务,第二种方式是基于注册中心进行引用。
Dubbo 服务引用的时机有两个,第一个是在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务,第二个是在 ReferenceBean 对应的服务被注入到其他类中时引用。这两个引用服务的时机区别在于,第一个是饿汉式的,第二个是懒汉式的。默认情况下,Dubbo 使用懒汉式引用服务。
下边是官网的一个服务引用的时序图:

  • 饿汉式:在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务。

  • 懒汉式:在 ReferenceBean 对应的服务被注入到其他类中时引用。Dubbo默认使用懒汉式。

ReferenceConfig:通过get方法其实是进入到ReferenceConfig类中执行init()方法。在这个方法里主要做了下面几件事情:

  • 1、对@Reference标注的接口查看是否合法,检查该接口是不是存在泛型。

  • 2、在系统中拿到dubbo.resolve.file这个文件,这个文件是进行配置consumer的接口的。将配置好的consumer信息存到URL中。

  • 3、将配置好的ApplicationConfig、ConsumerConfig、ReferenceConfig、MethodConfig,以及消费者的IP地址存到系统的上下文中。

  • 4、接下来开始创建代理对象进入到ReferenceConfig的createProxy 。这里还是在ReferenceConfig类中。上面的那些配置统统传入该方法中。上面有提到resolve解析consumer为URL,现在就根据这个URL首先判断是否远程调用还是本地调用。

    • 4.1、若是本地调用,则调用 InjvmProtocol 的 refer 方法生成 InjvmInvoker 实例。
    • 4.2、若是远程调用,则读取直连配置项,或注册中心 url,并将读取到的 url 存储到 urls 中。然后根据 urls 元素数量进行后续操作。若 urls 元素数量为1,则直接通过 Protocol 自适应拓展类即RegistryProtocol类或者DubboProtocol构建 Invoker 实例接口,这得看URL前面的是registry://开头,还是以dubbo://。若 urls 元素数量大于1,即存在多个注册中心或服务直连 url,此时先根据 url 构建 Invoker。然后再通过 Cluster 合并即merge多个 Invoker,最后调用 ProxyFactory 生成代理类。

RegistryProtocol:在refer方法中首先为 url 设置协议头,然后根据 url 参数加载注册中心实例。然后获取 group 配置,根据 group 配置决定 doRefer 第一个参数的类型。doRefer 方法创建一个 RegistryDirectory 实例,然后生成服务消费者链接,通过registry.register方法向注册中心注册消费者的链接,然后通过directory.subscribe向注册中心订阅 providers、configurators、routers 等节点下的数据。完成订阅后,RegistryDirectory 会收到这几个节点下的子节点信息。由于一个服务可能部署在多台服务器上,这样就会在 providers 产生多个节点,这个时候就需要 Cluster 将多个服务节点合并为一个,并生成一个 Invoker。

ProxyFactory:Invoker 创建完毕后,接下来要做的事情是为服务接口生成代理对象。有了代理对象,即可进行远程调用。代理对象生成的入口方法为的getProxy。获取需要创建的接口列表,组合成数组。而后将该接口数组传入 Proxy 的 getProxy 方法获取 Proxy 子类,然后创建 InvokerInvocationHandler 对象,并将该对象传给 newInstance 生成 Proxy 实例。InvokerInvocationHandler 实现 JDK 的 InvocationHandler 接口,具体的用途是拦截接口类调用。可以理解为AOP或拦截器。也就是在获取该对象之前会调用到Proxy实例而不会调用到服务提供者对应的类。

三、Dubbo源码

public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean,
        ApplicationContextAware, InitializingBean, DisposableBean {
        
    @Override
    public void afterPropertiesSet() throws Exception {

        // 初始化dubbo的配置
        prepareDubboConfigBeans();

        // 默认使用懒汉加载
        if (init == null) {
            init = false;
        }

        // 饿汉加载,即时引入服务
        if (shouldInit()) {
            getObject();
        }
    }
    
    @Override
    public Object getObject() {
        return get();
    }   
}

public class ReferenceConfig<T> extends ReferenceConfigBase<T> {

    private transient volatile T ref;
    
    public synchronized T get() {
        if (destroyed) {
            throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
        }
        // 检测 ref 是否为空,为空则通过 init 方法创建
        if (ref == null) {
            // 启动初始化操作 init 方法主要用于处理配置,以及调用 createProxy 生成代理类
            init();
        }
        return ref;
    }
}

在进行具体工作之前,需先进行配置检查与收集工作。接着根据收集到的信息决定服务用的方式,有三种,

  • 1、第一种是引用本地 (JVM) 服务。
  • 2、第二是通过直连方式引用远程服务。
  • 3、第三是通过注册中心引用远程服务。

public class ReferenceConfig<T> extends ReferenceConfigBase<T> {

    private transient volatile T ref;
    
    private transient volatile boolean initialized;

    private DubboBootstrap bootstrap;   
    
    public synchronized void init() {
        //避免重复加载
        if (initialized) {
            return;
        }

        //获取Dubbo核心容器
        if (bootstrap == null) {
            bootstrap = DubboBootstrap.getInstance();
            //进行Dubbo核心配置的加载和检查
            bootstrap.initialize();
        }
        //在对象创建后在使用其他配置模块配置对象之前检查对象配置并重写默认配置
        checkAndUpdateSubConfigs();
        //检查并生成sub配置和Local配置是否合法
        checkStubAndLocal(interfaceClass);
        //判断对象是否有mock并生成mock信息
        ConfigValidationUtils.checkMock(interfaceClass, this);
        //保存对象属性map信息
        Map<String, String> map = new HashMap<String, String>();
        map.put(SIDE_KEY, CONSUMER_SIDE);
        //添加版本信息,包含dubbo版本,release版本,timestamp运行时间戳和sid_key等信息
        ReferenceConfigBase.appendRuntimeParameters(map);
        //添加泛型 revision信息
        if (!ProtocolUtils.isGeneric(generic)) {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put(REVISION_KEY, revision);
            }
            //生成服务的代理对象,跟服务导出是一样,通过代理对象来代理,返回代理方法
            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if (methods.length == 0) {
                logger.warn("No method found in service interface " + interfaceClass.getName());
                map.put(METHODS_KEY, ANY_VALUE);
            } else {
                //添加需要代理的方法
                map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), COMMA_SEPARATOR));
            }
        }
        //添加interface名
        map.put(INTERFACE_KEY, interfaceName);
        //添加重试信息
        AbstractConfig.appendParameters(map, getMetrics());
        //检查获取并添加Application信息
        AbstractConfig.appendParameters(map, getApplication());
        //检查获取并添加Module信息
        AbstractConfig.appendParameters(map, getModule());
        // remove 'default.' prefix for configs from ConsumerConfig
        // appendParameters(map, consumer, Constant
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值