并不简单的代理,Dubbo是如何做服务引用的

简介: 并不简单的代理,Dubbo是如何做服务引用的

前言

我们在Dubbo与SpringCloud对比的那一期就说过,Dubbo并不满足于作为RPC框架,除了RPC所需要的内容,还提供了如服务治理等额外的支持。但是无奈,很多人对Dubbo的印象依旧停留在RPC框架。所以,我们在学习的初期,我们先不急着学习那些分支内容,还是专注其核心,即围绕RPC循序渐进的学习。那么上一篇我们讲了服务暴露,今天就一起来学习RPC桥梁的另一端:消费者,即服务的引用。当然,在此之前,还是希望大家带着问题来学习,比如说:


服务引用在底层替我们做了什么?

Spring怎么和服务引用融合的?



一、服务引用的介绍

在JAVA中,引用的概念是一直贯穿的。大部分情况,我们都是进行着对象引用。但是通过类比,我们不难得出服务引用的概念:服务引用是指在程序中使用另一个程序或服务提供的功能或数据的方式。通过服务引用,程序可以调用其他程序或服务提供的功能,获取返回值并将其用于自己的业务逻辑中


二、配置与模型

1. 服务引用配置

同服务暴露一样,实现服务引用的可以通过XML文件和注解两种途径来实现

XML 文件:

<dubbo:reference  interface="com.zhanfu.dubbo.demo.dubbo.api.DemoService" timeout="2000"/>

或者 @DubboReference 注解

@Component
public class Consumer implements CommandLineRunner {
    @DubboReference(timeout = 2000)
    private DemoService demoService;
    @Override
    public void run(String... args) throws Exception {
        String result = demoService.sayHello("战斧");
        System.out.println("收到消息:" + result);
    }
}

2. 整体实现模型

如果我们做了上面的配置,那么它是如何实现调用远方或本地的服务的呢?我们先简单概括一下:Dubbo 为我们的引用创建了一个代理对象,这个代理对象如同一个电话筒,当我们对电话筒说话的时候,它会将我们的话传到指定的人那里。同理,当我们调用这个代理对象时,它会把我们的调用请求传递到服务提供方那里,获得结果后,再反馈给我们,当然,这里仅仅是将其简单化成一个代理对象,实际其结合了容错策略后,实现还是很复杂的

d595747055d24bc6a2d441b0e1af2ba7.png


三. 引用的具体实现

如同前几期的一样,我们今天仍然以与Spring框架结合下的Dubbo进行分析


1. 触发创建代理对象

服务引用的操作其实和服务暴露一样,因此把上次服务暴露的图稍微改下各位就明白了

6ba2b92c616647cba742228f7cd311a7.png

与服务暴露仅有后置处理器不同:


服务暴露的工厂后置处理器为ServiceAnnotationPostProcessor ,而现在服务引用的工厂后置处理器为ReferenceAnnotationBeanPostProcessor,当然,其扫描的注解也有不同,服务引用扫描的注解为


org.apache.dubbo.config.annotation.DubboReference

org.apache.dubbo.config.annotation.Reference

com.alibaba.dubbo.config.annotation.Reference

至于容器刷新监听,其实都是由 DubboDeployApplicationListener 进行监听,最终也都会落在在同一个方法里

onContextRefreshedEvent -> deployer.start() -> startSync()

// DefaultModuleDeployer.class
private synchronized Future startSync() throws IllegalStateException {
    if (isStopping() || isStopped() || isFailed()) {
        throw new IllegalStateException(getIdentifier() + " is stopping or stopped, can not start again");
    }
    try {
        ......
        // 服务暴露
        exportServices();
        ......
        // 引用服务
        referServices();
        ......
    } catch (Throwable e) {
        onModuleFailed(getIdentifier() + " start failed: " + e, e);
        throw e;
    }
    return startFuture;
}

接下来,就让我们专注于真正的引用服务方法 DefaultModuleDeployer.referServices()

// DefaultModuleDeployer
private void referServices() {
    configManager.getReferences().forEach(rc -> {
        try {
            ReferenceConfig<?> referenceConfig = (ReferenceConfig<?>) rc;
            if (!referenceConfig.isRefreshed()) {
                referenceConfig.refresh();
            }
      // 每一个rc 其实就是一个引用配置,比如我们在DemoService上加了@DubboReference注解,就会被解析成一个引用配置,形如
      // <dubbo:reference sticky="false" timeout="2000" id="demoService" interface="com.zhanfu.dubbo.demo.dubbo.api.DemoService" />
            if (rc.shouldInit()) {
                if (referAsync || rc.shouldReferAsync()) {
                    ExecutorService executor = executorRepository.getServiceReferExecutor();
                    CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                        try {
                            referenceCache.get(rc);
                        } catch (Throwable t) {
                            logger.error(CONFIG_FAILED_EXPORT_SERVICE, "", "", "Failed to async export service config: " + getIdentifier() + " , catch error : " + t.getMessage(), t);
                        }
                    }, executor);
                    asyncReferringFutures.add(future);
                } else {
                  // 从引用缓存中尝试获取该引用配置的代理对象,注意此处仅仅执行该方法,并没有获取其返回值
                    referenceCache.get(rc);
                }
            }
        } catch (Throwable t) {
            logger.error(CONFIG_FAILED_REFERENCE_MODEL, "", "", "Model reference failed: " + getIdentifier() + " , catch error : " + t.getMessage(), t);
            referenceCache.destroy(rc);
            throw t;
        }
    });
}

上文中的 referenceCache 其实是一个标注了 final 的 SimpleReferenceCache 对象,其储存着所有的引用代理的情况,需要注意的是,真正的代理对象并不存储在它这里。

public <T> T get(ReferenceConfigBase<T> rc) {
  // 为该配置生成key, 即接口的全限定名,此处就是com.zhanfu.dubbo.demo.dubbo.api.DemoService
    String key = generator.generateKey(rc);
    // 接口的类型 com.zhanfu.dubbo.demo.dubbo.api.DemoService.calss
    Class<?> type = rc.getInterfaceClass();
    boolean singleton = rc.getSingleton() == null || rc.getSingleton();
    T proxy = null;
    // 单例类才需要从缓存取,不然每次都得新取一个
    if (singleton) {
        proxy = get(key, (Class<T>) type);
    } else {
        logger.warn(CONFIG_API_WRONG_USE, "", "", "Using non-singleton ReferenceConfig and ReferenceCache at the same time may cause memory leak. " +
            "Call ReferenceConfig#get() directly for non-singleton ReferenceConfig instead of using ReferenceCache#get(ReferenceConfig)");
    }
    // 第一次或者不是单例,创建代理信息
    if (proxy == null) {
      // 代理对象并不存储,存储的是引用与引用配置的映射
        List<ReferenceConfigBase<?>> referencesOfType = referenceTypeMap.computeIfAbsent(type, _t -> Collections.synchronizedList(new ArrayList<>()));
        referencesOfType.add(rc);
        List<ReferenceConfigBase<?>> referenceConfigList = referenceKeyMap.computeIfAbsent(key, _k -> Collections.synchronizedList(new ArrayList<>()));
        referenceConfigList.add(rc);
        // 真正创建代理的地方,还是由引用配置对象自己来实现的
        proxy = rc.get();
    }
    return proxy;
}

至此,结合Spring的项目,我们对服务引用的准备就完成了,接下来就是其核心内容——代理的创建


2. 创建代理的实现

紧接上文,我们看到了 rc.get() ,其实这里就是真正创建代理的地方

// ReferenceConfig.class
public T get() {
    if (destroyed) {
        throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
    }
    if (ref == null) {
        // 确保模块已启动,兼容老版本
        getScopeModel().getDeployer().start();
        synchronized (this) {
            if (ref == null) {
                init();
            }
        }
    }
    return ref;
}

init() 方法内容较多,我们仅看关键的一行,此处的referenceParameters包含了引用所需要的各种信息

 ref = createProxy(referenceParameters);

19d5e0333566c4ab791ce2c4f57396ccc.png


那么我们再进入 createProxy 一探究竟

ProxyFactory proxyFactory = this.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
private T createProxy(Map<String, String> referenceParameters) {
  // 创建本地调用器
    if (shouldJvmRefer(referenceParameters)) {
        createInvokerForLocal(referenceParameters);
    } else {
        urls.clear();
        meshModeHandleUrl(referenceParameters);
        if (StringUtils.isNotEmpty(url)) {
            // user specified URL, could be peer-to-peer address, or register center's address.
            parseUrl(referenceParameters);
        } else {
            // if protocols not in jvm checkRegistry
            if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())) {
                aggregateUrlFromRegistry(referenceParameters);
            }
        }
      // 创建远程调用器
        createInvokerForRemote();
    }
    URL consumerUrl = new ServiceConfigURL(CONSUMER_PROTOCOL, referenceParameters.get(REGISTER_IP_KEY), 0,
            referenceParameters.get(INTERFACE_KEY), referenceParameters);
    consumerUrl = consumerUrl.setScopeModel(getScopeModel());
    consumerUrl = consumerUrl.setServiceModel(consumerModel);
    // 发布订消费者元信息,注册到注册中心上
    MetadataUtils.publishServiceDefinition(consumerUrl, consumerModel.getServiceModel(), getApplicationModel());
    // 创建引用代理,此处的 proxyFactory 是Dubbo 的SPI接口,不是Spring提供的那个类
    return (T) proxyFactory.getProxy(invoker, ProtocolUtils.isGeneric(generic));
}

9b1df0c9281b40078f8e7cdfb22337b8.png

可以看到这又是一个自适应的代理工厂类,那么其具体采用那个实现类,就要看入参了,此刻我们采用的远程服务,使用的MigrationInvoker ,最终使用的是默认的 ”javassist“ ,即 org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory 实现类,并最终拼接成操作一个代理类,并将其实例化38fa96bf692e493bb6fd6b31bc441cbd.png

e38059d58f924464974c0c178c105710.png


这中间通过javaassit生成的过程十分繁杂,我们直接将生成的代理进行反编译,其代码如下

class DemoServiceDubboProxy0
{
   public DemoServiceDubboProxy0();
   public DemoServiceDubboProxy0(java.lang.reflect.InvocationHandler);
    public void $destroy();
    public java.lang.String sayHello(java.lang.String);
    public java.lang.Object $echo(java.lang.Object);
   public static [Ljava.lang.reflect.Method; methods;
   private java.lang.reflect.InvocationHandler handler;
}

因此,我们知道了,代理内其实还是一个 MigrationInvoker 在执行操作,但是我们现在并不看其内部在干什么,留待讲调用的章节再来详谈,因此点到为止,完成了一个代理对象的创建。


3. 代理的获取

完成了代理对象的创建,工作并没有结束,我们在业务代码中,还需要获取代理。当然在Spring框架下,更贴切的叫法叫做注入。把代理对象注入我们业务代码中。如下图

d9d5a11ae1484dc5b7833fb0a505e1df.png


我们首先要知道的是通过xml 或@DubboReference 注解标志的接口,最终会被解析成一个 ReferenceBean 对象。


public class ReferenceBean<T> implements FactoryBean<T>,
        ApplicationContextAware, BeanClassLoaderAware, BeanNameAware, InitializingBean, DisposableBean

因为其实现了FactoryBean 接口,所以我们应当能猜到重点并不是其本身,而是通过它的 getObject() 方法能获得什么东西。 (不了解FactoryBean 的看这里: BeanFactory 和 FactoryBean 的关联与区别)

public T getObject() {
        if (lazyProxy == null) {
            createLazyProxy();
        }
        return (T) lazyProxy;
}

果不其然,我们想要的东西很快就出现了,那就是代理对象的创建,我们关注到,这里其实提供的是一个惰性代理,这种设计是为了防止一些Bean过早产生,因为当时它的配置和环境未必已经准备完成,此时直接初始化该Bean可能会出现各种问题。(其实我们在开发中,也有很多时候会使用@Lazy 注解来达到类似的目的)那么这个代理是如何创建的呢?

private void createLazyProxy() {
  // 使用的是Spring提供的代理工厂
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.setTargetSource(new DubboReferenceLazyInitTargetSource());
    proxyFactory.addInterface(interfaceClass);
    // 代理还需要实现一些额外的接口,此处是 EchoService.class, Destroyable.class
    Class<?>[] internalInterfaces = AbstractProxyFactory.getInternalInterfaces();
    for (Class<?> anInterface : internalInterfaces) {
        proxyFactory.addInterface(anInterface);
    }
    if (!StringUtils.isEquals(interfaceClass.getName(), interfaceName)) {
        //add service interface
        try {
            Class<?> serviceInterface = ClassUtils.forName(interfaceName, beanClassLoader);
            proxyFactory.addInterface(serviceInterface);
        } catch (ClassNotFoundException e) {
            // generic call maybe without service interface class locally
        }
    }
    // 创建代理
    this.lazyProxy = proxyFactory.getProxy(this.beanClassLoader);
}

我们这里要注意一个重点,我们为 proxyFactory 代理工厂设置了一个目标对象 DubboReferenceLazyInitTargetSource,熟悉 SpringAop 的朋友应该知道 proxyFactory 的目标对象就是真正执行方法的对象,代理只是在调用该对象的前后加入了一些操作。那么我们来看看 DubboReferenceLazyInitTargetSource 有什么特殊之处

// ReferenceBean 的内部类
private class DubboReferenceLazyInitTargetSource extends AbstractLazyCreationTargetSource {
    @Override
    protected Object createObject() throws Exception {
        return getCallProxy();
    }
    @Override
    public synchronized Class<?> getTargetClass() {
        return getInterfaceClass();
    }
}

我们可以看到该对象最大的特点是其实现了 AbstractLazyCreationTargetSource ,两个方法也是重写的父类的。限于主题与篇幅,我们此处不针对该父类详细展开,仅对其作用进行描述:


AbstractLazyCreationTargetSource是 Spring Framework 中的一个抽象类,它实现了

TargetSource 接口。它的主要作用是作为一个懒加载的代理对象,在第一次调用它时,通过回调方法创建并缓存目标对象。


换而言之,它就是一个临时工,正主有事它先占着位置,等到真正需要工作时它再去叫正主,换句话说,如果我们仅是获取代理,里面的目标对象就是这个,但是如果我们调用了代理的方法,它就会通过createObject方法来叫真正的对象,我们可以看到,它是通过getCallProxy来叫真正的对象

private Object getCallProxy() throws Exception {
  // 惰性代理的作用,只有当环境与配置都准备完毕,才会真正产生可用的代理目标
    if (referenceConfig == null) {
        throw new IllegalStateException("ReferenceBean is not ready yet, please make sure to call reference interface method after dubbo is started.");
    }
    // 对象的创建需要给容器上锁,放置死锁
    synchronized (((DefaultSingletonBeanRegistry)getBeanFactory()).getSingletonMutex()) {
        return referenceConfig.get();
    }
}
// ReferenceConfig ,即引用配置
public T get() {
    if (destroyed) {
        throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
    }
    if (ref == null) {
        // ensure start module, compatible with old api usage
        getScopeModel().getDeployer().start();
        synchronized (this) {
            if (ref == null) {
                init();
            }
        }
    }
    return ref;
}

ce0e6f99671747599a5adcc44c5bbdc0.png


到这里,我们终于看到了返回的代理对象,即上文里的ref,而ref的诞生,我们在上一个小节,创建代理对象里已经讲过了。至此,业务代码如何获取的代理我们也看完了。


四、小结

通篇下来,我们看了大量的源码,并辅以文字解释,总算讲完了Dubbo是如何做服务引用的,一句话概括就是使用了代理,但是这个代理的设计与实现却很复杂,总结有三个原因:


全面的SPI自适应设计,使得代理工厂有好几个可选择

本地或者远程,会创建不同的invoker,即调用器

为了和Spring配合,使用了惰性代理避免过早的初始化而出错

目前我们已经把服务引用和服务暴露都讲了,虽然还有大量的细节有待补充,但其核心流程已经说完,相当于一座大桥的两头我们已经搭好,下一次,我们将进行这座大桥的合拢————即看看服务调用


目录
相关文章
|
5月前
|
XML Dubbo Java
【Dubbo3高级特性】「框架与服务」服务的异步调用实践以及开发模式
【Dubbo3高级特性】「框架与服务」服务的异步调用实践以及开发模式
137 0
|
2月前
|
JSON Dubbo Java
【Dubbo协议指南】揭秘高性能服务通信,选择最佳协议的终极攻略!
【8月更文挑战第24天】在分布式服务架构中,Apache Dubbo作为一款高性能的Java RPC框架,支持多种通信协议,包括Dubbo协议、HTTP协议及Hessian协议等。Dubbo协议是默认选择,采用NIO异步通讯,适用于高要求的内部服务通信。HTTP协议通用性强,利于跨语言调用;Hessian协议则在数据传输效率上有优势。选择合适协议需综合考虑性能需求、序列化方式、网络环境及安全性等因素。通过合理配置,可实现服务性能最优化及系统可靠性提升。
45 3
|
2月前
|
缓存 Dubbo Java
Dubbo服务消费者启动与订阅原理
该文章主要介绍了Dubbo服务消费者启动与订阅的原理,包括服务消费者的启动时机、启动过程以及订阅和感知最新提供者信息的方式。
Dubbo服务消费者启动与订阅原理
|
2月前
|
Dubbo 网络协议 Java
深入掌握Dubbo服务提供者发布与注册原理
该文章主要介绍了Dubbo服务提供者发布与注册的原理,包括服务发布的流程、多协议发布、构建Invoker、注册到注册中心等过程。
深入掌握Dubbo服务提供者发布与注册原理
|
2月前
|
负载均衡 Dubbo Java
Dubbo服务Spi机制和原理
该文章主要介绍了Dubbo中的SPI(Service Provider Interface)机制和原理,包括SPI的基本概念、Dubbo中的SPI分类以及SPI机制的实现细节。
Dubbo服务Spi机制和原理
|
2月前
|
C# 开发者 Windows
勇敢迈出第一步:手把手教你如何在WPF开源项目中贡献你的第一行代码,从选择项目到提交PR的全过程解析与实战技巧分享
【8月更文挑战第31天】本文指导您如何在Windows Presentation Foundation(WPF)相关的开源项目中贡献代码。无论您是初学者还是有经验的开发者,参与这类项目都能加深对WPF框架的理解并拓展职业履历。文章推荐了一些适合入门的项目如MvvmLight和MahApps.Metro,并详细介绍了从选择项目、设置开发环境到提交代码的全过程。通过具体示例,如添加按钮点击事件处理程序,帮助您迈出第一步。此外,还强调了提交Pull Request时保持专业沟通的重要性。参与开源不仅能提升技能,还能促进社区交流。
32 0
|
2月前
|
缓存 负载均衡 Dubbo
Dubbo服务集群容错原理(重要)
该文章主要介绍了Dubbo服务集群容错的原理,包括集群容错技术的概念、Dubbo中使用的集群容错技术种类及其原理。
|
2月前
|
负载均衡 Dubbo 算法
Dubbo服务负载均衡原理
该文章主要介绍了Dubbo服务负载均衡的原理,包括Dubbo中负载均衡的实现位置、为什么需要负载均衡机制、Dubbo支持的负载均衡算法以及随机负载均衡策略的源码分析。
|
4月前
|
Dubbo 前端开发 Java
Dubbo3 服务原生支持 http 访问,兼具高性能与易用性
本文展示了 Dubbo3 triple 协议是如何简化从协议规范与实现上简化开发测试、入口流量接入成本的,同时提供高性能通信、面向接口的易用性编码。
16610 14
|
5月前
|
XML Dubbo Java
【Dubbo3高级特性】「框架与服务」 Nacos作为注册中心-服务分组及服务分组聚合实现
【Dubbo3高级特性】「框架与服务」 Nacos作为注册中心-服务分组及服务分组聚合实现
227 0
下一篇
无影云桌面