Dubbo 3.x源码(18)—Dubbo服务引用源码(1)

基于Dubbo 3.1,详细介绍了Dubbo服务的发布与引用的源码。

此前我们学习了Dubbo的服务导出的源码,在DubboBootstrapApplicationListener#startSync方法中,在调用了exportServices方法进行服务导出之后,立即调用了referServices方法进行服务引用,所以说Dubbo3.1中,服务导出和服务引用的入口是相同的,都是DubboDeployApplicationListener这个监听器。

在spring容器启动的最后一个步也就是refresh方法内部最后的finishRefresh方法中,将会向所有监听器发布一个ContextRefreshedEvent事件,表示容器刷新完毕。DubboDeployApplicationListener会监听该事件,进而触发服务的导出和引用。

Dubbo 3.x服务引用源码:

  1. Dubbo 3.x源码(11)—Dubbo服务的发布与引用的入口
  2. Dubbo 3.x源码(18)—Dubbo服务引用源码(1)

Dubbo 3.x服务发布源码:

  1. Dubbo 3.x源码(11)—Dubbo服务的发布与引用的入口
  2. Dubbo 3.x源码(12)—Dubbo服务发布导出源码(1)
  3. Dubbo 3.x源码(13)—Dubbo服务发布导出源码(2)
  4. Dubbo 3.x源码(14)—Dubbo服务发布导出源码(3)
  5. Dubbo 3.x源码(15)—Dubbo服务发布导出源码(4)
  6. Dubbo 3.x源码(16)—Dubbo服务发布导出源码(5)
  7. Dubbo 3.x源码(17)—Dubbo服务发布导出源码(6)

1 referServices引用服务入口

该方法获取全部ReferenceConfigBase实例并遍历,判断是否应该初始化,判断init属性,默认true,继续判断是否异步引用,默认同步。最后通过引用缓存对象来进行服务引用,即referenceCache.get(rc)方法实现。

这个init属性在Dubbo2.x版本引入,该值用于判断是否在afterPropertiesSet()时饥饿初始化引用,否则等到有人注入或引用该实例时再初始化,默认false,即懒加载。但是在Dubbo3.1版本中,init仅被用于在shouldInit方法中,而且默认返回true,不再是默认懒加载,只有手动设置为false,才不会引入服务。因为此时dubbo服务的引入已不在ReferenceBean的 afterPropertiesSet方法中。

/**
 * DefaultModuleDeployer的方法
 * <p>
 * 服务引用
 */
private void referServices() {
    //获取全部ReferenceConfigBase实例并遍历
    configManager.getReferences().forEach(rc -> {
        try {
            ReferenceConfig<?> referenceConfig = (ReferenceConfig<?>) rc;
            //如果还没刷新该引用配置
            if (!referenceConfig.isRefreshed()) {
                //刷新配置,即Dubbo配置的重写(基于优先级的覆盖)
                //设个方法我们在此前Dubbo配置的加载部分已经讲过了
                referenceConfig.refresh();
            }
            //是否应该初始化,判断init属性,默认true
            if (rc.shouldInit()) {
                //如果模块消费端开启异步调用,或者消费者开启异步调用,默认都是false,即同步调用
                if (referAsync || rc.shouldReferAsync()) {
                    ExecutorService executor = executorRepository.getServiceReferExecutor();
                    CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                        try {
                            referenceCache.get(rc);
                        } catch (Throwable t) {
                            logger.error("5-9", "", "", "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("5-15", "", "", "Model reference failed: " + getIdentifier() + " , catch error : " + t.getMessage(), t);
            referenceCache.destroy(rc);
            throw t;
        }
    });
}

2 SimpleReferenceCache#get获取缓存

SimpleReferenceCache是一个对于单例的ReferenceConfigBase的缓存对象。该方法中的大概逻辑为:

  1. 首先构建缓存key,规则为 {group}/{InterfaceName}:{version} ,随后获取引用的服务接口Class。
  2. 判断是否是单例服务,如果是那么首先尝试从缓存中获取已创建的服务代理实例对象proxy。
  3. 如果没有缓存,那么将ReferenceConfigBase存入referenceTypeMap和referenceKeyMap这两个缓存map中,随后调用ReferenceConfigBase自身的get方法进行服务引用,获取服务代理实例对象。

可以看到,虽然我们是调用的SimpleReferenceCache#get方法,但是其内部会判断如果没有进行服务引用,那么会引用服务,间接的达成了我们服务引用的目的,同时还构建了缓存。而服务引用的关键方法就是ReferenceConfigBase#get方法。

/**
 * SimpleReferenceCache的方法
 *
 * @param rc 服务消费者引用服务配置
 * @return 服务代理实例对象
 */
@Override
@SuppressWarnings("unchecked")
public <T> T get(ReferenceConfigBase<T> rc) {
    //获取缓存key,规则为 {group}/{InterfaceName}:{version} 
    String key = generator.generateKey(rc);
    //获取引用的服务接口Class
    Class<?> type = rc.getInterfaceClass();
    //是否是单例,默认true
    boolean singleton = rc.getSingleton() == null || rc.getSingleton();
    T proxy = null;
    // Check existing proxy of the same 'key' and 'type' first.
    //如果是单例
    if (singleton) {
        //那么首先尝试从缓存中获取已创建的服务代理实例对象
        proxy = get(key, (Class<T>) type);
    } else {
        logger.warn("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) {
        //引用服务类型到服务消费者引用服务配置的缓存referenceTypeMap
        List<ReferenceConfigBase<?>> referencesOfType = referenceTypeMap.computeIfAbsent(type, _t -> Collections.synchronizedList(new ArrayList<>()));
        referencesOfType.add(rc);
        //引用缓存key到服务消费者引用服务配置的缓存referenceKeyMap
        List<ReferenceConfigBase<?>> referenceConfigList = referenceKeyMap.computeIfAbsent(key, _k -> Collections.synchronizedList(new ArrayList<>()));
        referenceConfigList.add(rc);
        /*
         * 调用ReferenceConfigBase自身的get方法进行服务引用,获取服务代理实例对象
         */
        proxy = rc.get();
    }
    //返回服务代理实例对象,不需要特意存入某个换尺寸,因为每个ReferenceConfigBase自身会存储它的取服务代理实例对象
    return proxy;
}

3 ReferenceConfig#get服务引用

该方法获取接口代理引用对象实例,这里面使用双重检测锁来判断ref是否为null,如果为null,说明该服务还没有进行服务引用,那么调用init方法初始化引用服务。

/**
 * ReferenceConfig的方法
 *
 * @return 代理引用服务实例
 */
@Override
public T get() {
    //销毁检查
    if (destroyed) {
        throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
    }
    //如果接口代理引用为null
    if (ref == null) {
        // ensure start module, compatible with old api usage
        //确保启动模块,与旧的API使用兼容
        getScopeModel().getDeployer().start();
        //双重校验锁
        synchronized (this) {
            //如果接口代理引用为null
            if (ref == null) {
                //初始化引用服务
                init();
            }
        }
    }

    return ref;
}

4 ReferenceConfig#init初始化服务引用

该方法用于初始化某个服务引用,大概逻辑为:

  1. 服务引用前初始化serviceMetadata服务元数据。
  2. 注册serviceDescriptor服务描述符到本地内存的服务仓库ModuleServiceRepositoryservices缓存中,即注册service
  3. 构建此服务对应的服务消费者模型consumerModel,并且注册到本地内存的服务仓库ModuleServiceRepositoryconsumers缓存中,即注册consumer
  4. 调用createProxy方法,根据服务引用参数map创建服务接口代理引用对象,并赋值给res。这是核心逻辑。
  5. 设置服务元数据的服务接口代理引用对象。
/**
 * ReferenceConfig的方法
 * <p>
 * 初始化引用服务
 */
protected synchronized void init() {
    //如果已被初始化,那么直接返回
    if (initialized && ref != null) {
        return;
    }
    try {
        //如果还没刷新该引用配置
        if (!this.isRefreshed()) {
            //刷新配置,即Dubbo配置的重写(基于优先级的覆盖)
            //设个方法我们在此前Dubbo配置的加载部分已经讲过了
            this.refresh();
        }
        /*
         * 1 服务引用前初始化serviceMetadata服务元数据
         */
        // init serviceMetadata
        //根据consumer初始化服务元数据
        initServiceMetadata(consumer);
        //设置服务类型
        serviceMetadata.setServiceType(getServiceInterfaceClass());
        // TODO, uncomment this line once service key is unified
        //服务key: group/服务接口:版本号
        serviceMetadata.generateServiceKey();
        //附加服务引用所需的所有配置
        Map<String, String> referenceParameters = appendConfig();
        // init service-application mapping 从本地存储和url参数初始化服务应用程序映射
        //也就是MetadataServiceNameMapping
        initServiceAppsMapping(referenceParameters);
        //获取Module级别的服务存储仓库,其内部保存着服务提供者和服务消费者的缓存
        ModuleServiceRepository repository = getScopeModel().getServiceRepository();
        /*
         * 2 注册serviceDescriptor服务描述符到本地内存的服务仓库ModuleServiceRepository的services缓存中,即注册service
         */
        //服务描述符,通过它可以获取服务描述信息,例如服务提供的方法,服务接口名,服务接口Class等
        ServiceDescriptor serviceDescriptor;
        if (CommonConstants.NATIVE_STUB.equals(getProxy())) {
            serviceDescriptor = StubSuppliers.getServiceDescriptor(interfaceName);
            repository.registerService(serviceDescriptor);
        } else {
            //注册服务描述符到服务仓库内部的services集合中
            serviceDescriptor = repository.registerService(interfaceClass);
        }
        /*
         * 3 构建此服务对应的服务消费者模型consumerModel,并且注册到本地内存的服务仓库ModuleServiceRepository的consumers缓存中,即注册consumer
         */
        consumerModel = new ConsumerModel(serviceMetadata.getServiceKey(),
            //生成动态代理的策略,可以选择两种策略:jdk和javassist
            proxy,
            //服务描述符
            serviceDescriptor,
            //域模型
            getScopeModel(),
            //服务元数据
            serviceMetadata,
            //转换和聚合异步方法信息
            createAsyncMethodInfo(),
            //服务接口类加载器
            interfaceClassLoader);

        // Compatible with dependencies on ServiceModel#getReferenceConfig() , and will be removed in a future version.
        //兼容ServiceModel#getReferenceConfig()上的依赖项,并将在未来的版本中删除。
        consumerModel.setConfig(this);
        //注册服务消费者模型到服务仓库内部的consumers集合中
        repository.registerConsumer(consumerModel);
        //将引用参数存入服务元数据的附加数据中
        serviceMetadata.getAttachments().putAll(referenceParameters);
        /*
         * 4 根据服务引用参数创建服务接口代理引用对象
         */
        ref = createProxy(referenceParameters);
        //设置服务元数据的服务接口代理引用对象
        serviceMetadata.setTarget(ref);
        serviceMetadata.addAttribute(PROXY_CLASS_REF, ref);

        consumerModel.setDestroyRunner(getDestroyRunner());
        consumerModel.setProxyObject(ref);
        consumerModel.initMethodModels();
        //检查调用程序可用
        checkInvokerAvailable();
    } catch (Throwable t) {
        try {
            //销毁invoker
            if (invoker != null) {
                invoker.destroy();
            }
        } catch (Throwable destroy) {
            logger.warn("5-3", "", "", "Unexpected error occurred when destroy invoker of ReferenceConfig(" + url + ").", t);
        }
        //取消注册consumerModel
        if (consumerModel != null) {
            ModuleServiceRepository repository = getScopeModel().getServiceRepository();
            repository.unregisterConsumer(consumerModel);
        }
        //还原属性
        initialized = false;
        invoker = null;
        ref = null;
        consumerModel = null;
        serviceMetadata.setTarget(null);
        serviceMetadata.getAttributeMap().remove(PROXY_CLASS_REF);

        // Thrown by checkInvokerAvailable().
        if (t.getClass() == IllegalStateException.class &&
            t.getMessage().contains("No provider available for the service")) {

            // 2-2 - No provider available.
            logger.error("2-2", "server crashed", "", "No provider available.", t);
        }

        throw t;
    }
    initialized = true;
}

4.1 appendConfig获取服务引用参数

该方法用于获取当前的服务引用参数,用于后续的createProxy方法创建服务代理引用对象。

/**
 * ReferenceConfig的方法
 * <p>
 * 附加服务引用所需的所有配置到一个map中
 *
 * @return 引用参数
 */
private Map<String, String> appendConfig() {
    Map<String, String> map = new HashMap<>(16);
    //interface -> 服务接口全路径名
    map.put(INTERFACE_KEY, interfaceName);
    //side -> consumer  表示消费端
    map.put(SIDE_KEY, CONSUMER_SIDE);
    //添加运行时参数
    //dubbo -> Dubbo RPC协议版本,默认2.0.2
    //release -> Dubbo的实现版本,通常是jar版本
    //timestamp -> 当前时间戳毫秒值
    //pid -> 当前服务进程pid
    ReferenceConfigBase.appendRuntimeParameters(map);
    //非泛化接口
    if (!ProtocolUtils.isGeneric(generic)) {
        String revision = Version.getVersion(interfaceClass, version);
        if (StringUtils.isNotEmpty(revision)) {
            map.put(REVISION_KEY, revision);
        }

        String[] methods = methods(interfaceClass);
        if (methods.length == 0) {
            logger.warn("5-4", "", "", "No method found in service interface: " + interfaceClass.getName());
            map.put(METHODS_KEY, ANY_VALUE);
        } else {
            map.put(METHODS_KEY, StringUtils.join(new HashSet<>(Arrays.asList(methods)), COMMA_SEPARATOR));
        }
    }
    //Application配置
    AbstractConfig.appendParameters(map, getApplication());
    //Module配置
    AbstractConfig.appendParameters(map, getModule());
    //consumer配置
    AbstractConfig.appendParameters(map, consumer);
    //reference配置
    AbstractConfig.appendParameters(map, this);
    appendMetricsCompatible(map);

    String hostToRegistry = ConfigUtils.getSystemProperty(DUBBO_IP_TO_REGISTRY);
    if (StringUtils.isEmpty(hostToRegistry)) {
        hostToRegistry = NetUtils.getLocalHost();
    } else if (isInvalidLocalHost(hostToRegistry)) {
        throw new IllegalArgumentException(
            "Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
    }

    map.put(REGISTER_IP_KEY, hostToRegistry);

    if (CollectionUtils.isNotEmpty(getMethods())) {
        for (MethodConfig methodConfig : getMethods()) {
            AbstractConfig.appendParameters(map, methodConfig, methodConfig.getName());
            String retryKey = methodConfig.getName() + ".retry";
            if (map.containsKey(retryKey)) {
                String retryValue = map.remove(retryKey);
                if ("false".equals(retryValue)) {
                    map.put(methodConfig.getName() + ".retries", "0");
                }
            }
        }
    }

    return map;
}

5 总结

本次我们学习了Dubbo 3.x服务引用的入口源码。我们知道ReferenceConfig#init用于初始化服务引用,其中调用createProxy方法,根据服务引用参数map创建服务接口代理引用对象,并赋值给res,这是核心逻辑,我们下文再学习!

  • 13
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘Java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值