【dubbo系列】 12-Dubbo 服务导出

前言

Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 收到事件后,会立即执行导出逻辑。整个逻辑大概可以分成三个部分:每一部分是前置工作,主要用于检查参数和组装URL。第二部分是导出服务,包含导出到本地(JVM),和导出服务到远程。第三个部分是向注册中心注册服务,用于服务发现。以下源码分析基于 Dubbo 2.7.2。

源码分析

服务导出的入口在 ServiceBeanonApplicationEventonApplicationEvent 是一个事件响应方法,该方法会在收到 Spring 上下文刷新事件后执行服务导出操作。

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
    // 判断是否已经导出或者已经取消导出
    if (!isExported() && !isUnexported()) {
        if (logger.isInfoEnabled()) {
            logger.info("The service ready on spring started. service: " + getInterface());
        }
        export();
    }
}

这个方法主要是检查是否需要导出服务,如果服务已经导出了,或者已经取消导出的话,就不需要导出服务了。旧的Dubbo 版本这里还有一个是延迟导出的判断,在最新版的 Dubbo 中,这个检查条件放在了后面。

我们在来看 export 中的源码:

@Override
public void export() {
    super.export();
    // Publish ServiceBeanExportedEvent
    publishExportEvent();
}

/**
 * 父类 ServiceConfig 的 export 方法
 */
public synchronized void export() {
    // 检查和更新检查
    checkAndUpdateSubConfigs();
    // 是否需要导出
    if (!shouldExport()) {
        return;
    }
    // 是否需要延迟导出
    if (shouldDelay()) {
        delayExportExecutor.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
    } else {
        doExport();
    }
}

前置工作

前置工作主要是包含配置检查以及 URL 组装。在导出服务前,Dubbo 需要检查用户的配置是否合理,或者为用户补充缺省配置。配置检查完成以后,需要把这些配置组装到 URL 中。在 Dubbo 中,URL 的作用非常重要,Dubbo 使用 URL 作为配置载体,所有的拓展点都是通过 URL 获得配置。采用 URL 作为配置信息的统一格式,所有的扩展点都是通过传递 URL 携带配置信息。

检查和更新配置

我们从 export 方法中的 checkAndUpdateSubConfigs 中开始分析。

public void checkAndUpdateSubConfigs() {
    // Use default configs defined explicitly on global configs
    // 使用全局配置来显示定义默认配置。
    completeCompoundConfigs();
    // Config Center should always being started first.
    // 配置中心首先应该被启动
    startConfigCenter();
    // 检查默认项
    checkDefault();
    // 检查协议
    checkProtocol();
    // 检查应用信息
    checkApplication();
    // if protocol is not injvm checkRegistry
    // 检查 protocol 是否是本地协议
    if (!isOnlyInJvm()) {
        // 如果不是本地协议,则需要验证注册中心
        checkRegistry();
    }
    this.refresh();
    checkMetadataReport();

    if (StringUtils.isEmpty(interfaceName)) {
        throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
    }

    if (ref instanceof GenericService) {
        interfaceClass = GenericService.class;
        if (StringUtils.isEmpty(generic)) {
            generic = Boolean.TRUE.toString();
        }
    } else {
        try {
            interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                    .getContextClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        checkInterfaceAndMethods(interfaceClass, methods);
        checkRef();
        generic = Boolean.FALSE.toString();
    }
    if (local != null) {
        if ("true".equals(local)) {
            local = interfaceName + "Local";
        }
        Class<?> localClass;
        try {
            localClass = ClassUtils.forNameWithThreadContextClassLoader(local);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        if (!interfaceClass.isAssignableFrom(localClass)) {
            throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
        }
    }
    if (stub != null) {
        if ("true".equals(stub)) {
            stub = interfaceName + "Stub";
        }
        Class<?> stubClass;
        try {
            stubClass = ClassUtils.forNameWithThreadContextClassLoader(stub);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        if (!interfaceClass.isAssignableFrom(stubClass)) {
            throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
        }
    }
    checkStubAndLocal(interfaceClass);
    checkMock(interfaceClass);
}

我们开始一步一步的分析上面的源码。

  • 获取全局配置信息 completeCompoundConfigs

首先我们检测 provider 、 application 等核心配置类对象是否为空。如果为空的话,则尝试从其它配置类对象中获得相应的实例。

private void completeCompoundConfigs() {
    // provider 不为空,指定了 provider
    if (provider != null) {
        if (application == null) {
            setApplication(provider.getApplication());
        }
        if (module == null) {
            setModule(provider.getModule());
        }
        if (registries == null) {
            setRegistries(provider.getRegistries());
        }
        if (monitor == null) {
            setMonitor(provider.getMonitor());
        }
        if (protocols == null) {
            setProtocols(provider.getProtocols());
        }
        if (configCenter == null) {
            setConfigCenter(provider.getConfigCenter());
        }
    }
    // 指定 module
    if (module != null) {
        if (registries == null) {
            setRegistries(module.getRegistries());
        }
        if (monitor == null) {
            setMonitor(module.getMonitor());
        }
    }
    // 指定 application
    if (application != null) {
        if (registries == null) {
            setRegistries(application.getRegistries());
        }
        if (monitor == null) {
            setMonitor(application.getMonitor());
        }
    }
}
  • 获取配置中心 startConfigCenter

这个是 dubbo 2.7 以后新增的特性。新增了配置中心,应该始终先启动。

void startConfigCenter() {
        // 配置中心是否存在
        if (configCenter == null) {
            ConfigManager.getInstance().getConfigCenter().ifPresent(cc -> this.configCenter = cc);
        }

        if (this.configCenter != null) {
            // TODO there may have duplicate refresh
            this.configCenter.refresh();
            // 准备环境
            prepareEnvironment();
        }
        ConfigManager.getInstance().refreshAll();
    }
  • checkDefault

检测 provider 是否存在,如果不存在则用系统变量创建一个。

private void checkDefault() {
        createProviderIfAbsent();
    }

private void createProviderIfAbsent() {
    if (provider != null) {
        return;
    }
    setProvider(
        ConfigManager.getInstance()
                .getDefaultProvider()
                .orElseGet(() -> {
                    ProviderConfig providerConfig = new ProviderConfig();
                    providerConfig.refresh();
                    return providerConfig;
                })
    );
}
  • checkProtocol

检查协议,如果不存在则创建一个。可以通过 dubbo:protocoldubbo:providerdubbo:service 定义协议。


private void checkProtocol() {
    if (CollectionUtils.isEmpty(protocols) && provider != null) {
        setProtocols(provider.getProtocols());
    }
    convertProtocolIdsToProtocols();
}

private void convertProtocolIdsToProtocols() {
    if (StringUtils.isEmpty(protocolIds) && CollectionUtils.isEmpty(protocols)) {
        // 从环境变量加载 protocol
        List<String> configedProtocols = new ArrayList<>();
        configedProtocols.addAll(getSubProperties(Environment.getInstance()
                .getExternalConfigurationMap(), PROTOCOLS_SUFFIX));
        configedProtocols.addAll(getSubProperties(Environment.getInstance()
                .getAppExternalConfigurationMap(), PROTOCOLS_SUFFIX));

        protocolIds = String.join(",", configedProtocols);
    }

    if (StringUtils.isEmpty(protocolIds)) {
        if (CollectionUtils.isEmpty(protocols)) {
            setProtocols(
                    ConfigManager.getInstance().getDefaultProtocols()
                            .filter(CollectionUtils::isNotEmpty)
                            .orElseGet(() -> {
                                ProtocolConfig protocolConfig = new ProtocolConfig();
                                protocolConfig.refresh();
                                return new ArrayList<>(Arrays.asList(protocolConfig));
                            })
            );
        }
    } else {
        String[] arr = COMMA_SPLIT_PATTERN.split(protocolIds);
        List<ProtocolConfig> tmpProtocols = CollectionUtils.isNotEmpty(protocols) ? protocols : new ArrayList<>();
        Arrays.stream(arr).forEach(id -> {
            if (tmpProtocols.stream().noneMatch(prot -> prot.getId().equals(id))) {
                tmpProtocols.add(ConfigManager.getInstance().getProtocol(id).orElseGet(() -> {
                    ProtocolConfig protocolConfig = new ProtocolConfig();
                    protocolConfig.setId(id);
                    protocolConfig.refresh();
                    return protocolConfig;
                }));
            }
        });
        if (tmpProtocols.size() > arr.length) {
            throw new IllegalStateException("Too much protocols found, the protocols comply to this service are :" + protocolIds + " but got " + protocols
                    .size() + " registries!");
        }
        setProtocols(tmpProtocols);
    }
}
  • checkApplication

检查应用信息,即 dubbo:application 定义的内容。

protected void checkApplication() {
    // for backward compatibility
    // 不存在则创建
    createApplicationIfAbsent();

    // 判断是否有效,需要包含 name
    if (!application.isValid()) {
        throw new IllegalStateException("No application config found or it's not a valid config! " +
                "Please add <dubbo:application name=\"...\" /> to your spring config.");
    }

    ApplicationModel.setApplication(application.getName());

    // backward compatibility
    String wait = ConfigUtils.getProperty(SHUTDOWN_WAIT_KEY);
    if (wait != null && wait.trim().length() > 0) {
        System.setProperty(SHUTDOWN_WAIT_KEY, wait.trim());
    } else {
        wait = ConfigUtils.getProperty(SHUTDOWN_WAIT_SECONDS_KEY);
        if (wait != null && wait.trim().length() > 0) {
            System.setProperty(SHUTDOWN_WAIT_SECONDS_KEY, wait.trim());
        }
    }
}
  • checkRegistry

如果 protocol 不是 injvm , 即不是本地协议,需要检查注册中心。

protected void checkRegistry() {
    loadRegistriesFromBackwardConfig();

    convertRegistryIdsToRegistries();

    for (RegistryConfig registryConfig : registries) {
        if (!registryConfig.isValid()) {
            throw new IllegalStateException("No registry config found or it's not a valid config! " +
                    "The registry config is: " + registryConfig);
        }
    }

    useRegistryForConfigIfNecessary();
}

   转载规则


《【dubbo系列】 12-Dubbo 服务导出》 孤独如梦 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Redis 集群之主备架构(哨兵机制)原理及安装部署 Redis 集群之主备架构(哨兵机制)原理及安装部署
前言之前的文章介绍(Redis 集群之主从架构原理及安装部署)主从架构会有这样一个问题,如果 master node 因为某些原因宕机了,那么整个 redis 集群就无法进行写操作了。需要运维人员手动进行切换,然后所有程序需要修改配置然后重
2019-07-19
下一篇 
Redis 集群之主从架构原理及安装部署 Redis 集群之主从架构原理及安装部署
为什么需要集群?Redis 单机能够支持的 QPS 大概在 几万左右,具体是多少和服务器的配置以及业务的操作有关。但如果你的应用的 需要支撑上十万的 QPS 时,单机的 Redis 是无法支撑这么大的 QPS 的,如果卡死在 Redis,那
2019-07-17
  目录