前言:

本文主要讲解dubbo集成spring进行的服务暴露过程。大致流程我从处理标签开始讲解处理,从spring解析我们dubbo的xml文件到暴露服务的一个大致过程,(注:对spring的扩展点有一点了解。)后续流程发布细节讲持续更新发布。文中如有错误点请各位大佬指出,当前文章也只是本人在阅读源码当中的一个记录点。下面我们进入正文。

 服务导出简要流程如下:

  1. 读取配置信息生成ServiceBean -> ServiceConfig.export()
  2. 服务注册根据配置信息将服务以及相关信息注册到注册中心->RegistryProtocol.export()
  3. 根据配置的协议不同,启动Netty或Tomcat
  4. Dubbo服务提供者启动监听动态配置修改
  5. 服务导出完成,发布导出完成事件

DubboBootstrapApplicationListener监听器,内部内部初始化了dubbo的启动类 DubboBootstrap。

其中DubboBootstrapApplicationListener的监听器会由spring来进行对应的回调,在回调的时候进行了对应的服务注册。其中使用到了启动类DubboBootstrap

启动类的主要作用如下:

  1. 初始化configManagerenvironment对象。
  2. 注册DubboShutdownHook钩子,实现优雅停机。
  3. 更新配置中心ConfigCenterBean对象对应的值。
  4. 加载并更新远程配置。
  5. 检查configManager对象各个配置对象是否合法。
  6. 加载并初始化元数据中心。
  7. Dubbo服务导出。

DubboBootstrap内置2个关键配置类ConfigManager,Environment这2个类都是于整个dubbo服务的配置信息。核心暴露方法为DubboBootstrap.start()

public DubboBootstrap start() {
        if (started.compareAndSet(false, true)) {
            // ......省略代码
            initialize();
            // ......省略代码
            exportServices();
        }
        return this;
    }

start()方法内部的initialize()和exportServices()方法,该2个方法和我们暴露服务的过程息息相关,具体细致的源码各位可以GitHub拉取对应源码进行阅读。

initialize()该方法为Dubbo初始化配置、环境、元数据等,具体方法如下:

public void initialize() {
        if (!initialized.compareAndSet(false, true)) {
            return;
        }
        // 初始化环境,就是我们读取到的配置文件信息,ConfigManager类和Environment
        ApplicationModel.initFrameworkExts();
        // 开启配置中心
        startConfigCenter();
        // 加载远程配置详细
        loadRemoteConfigs();
        // 验证全局配置是否合法
        checkGlobalConfigs();
        // @since 2.7.8,开启元数据中心
        startMetadataCenter();
        // 初始化元数据中心
        initMetadataService();
        if (logger.isInfoEnabled()) {
            (NAME + " has been initialized!");
        }
    }

上面也有介绍到我们对应的configManager类,其中该类就保存着我们解析过后的配置信息,不管是标签也好还是注解也好,所有的配置信息都会存储在该管理器内部,顾名思义。内部由一个map进行存储着所有的配置信息。

final Map<String, Map<String, AbstractConfig>> configsCache = newMap();

完成上述步骤我们整个的dubbo所需要的环境是已经准备好了,下面就是我们注册服务和暴露服务的一个部分了。

exportServices()该方法主要用于暴露服务,其中注册,导出等一系列核心逻辑都在该方法完成。

private void exportServices() {
        // 从配置管理器中获取所有关于暴露服务的配置信息类
        //  <dubbo:service interface="org.apache.dubbo.demo.DemoService" timeout="3000" ref="demoService" registry="myRegistry, ztoRegistry"/>
        configManager.getServices().forEach(sc -> {
            // 服务配置类,顾名思义存储我们服务暴露的配置信息,其实该类为ServiceBean->ServiceConfig类
            ServiceConfig serviceConfig = (ServiceConfig) sc;
            serviceConfig.setBootstrap(this);
            // 是否开启异步导出服务,其实都是调用exportService来导出我们的服务
            if (exportAsync) {
                ExecutorService executor = executorRepository.getServiceExporterExecutor();
                Future<?> future = executor.submit(() -> {
                    try {
                        exportService(serviceConfig);
                    } catch (Throwable t) {
                        logger.error("export async catch error : " + t.getMessage(), t);
                    }
                });
                asyncExportingFutures.add(future);
            } else {
                // 导出服务(暴露服务)
                exportService(serviceConfig);
            }
        });
    }
private void exportService(ServiceConfig sc) {
        if (exportedServices.containsKey(sc.getServiceName())) {
            throw new IllegalStateException("There are multiple ServiceBean instances with the same service name: [" +
                    sc.getServiceName() + "], instances: [" +
                    exportedServices.get(sc.getServiceName()).toString() + ", " +
                    sc.toString() + "]. Only one service can be exported for the same triple (group, interface, version), " +
                    "please modify the group or version if you really need to export multiple services of the same interface.");
        }
        // 导出暴露服务
        sc.export();
        exportedServices.put(sc.getServiceName(), sc);
    }

checkAndUpdateSubConfigs(),元数据的处理initServiceMetadata(),和争对服务的导出doExport()方法。源码如下:

@Override
    public synchronized void export() {
        if (bootstrap == null) {
            bootstrap = DubboBootstrap.getInstance();
            // compatible with api call.
            if (null != this.getRegistry()) {
                bootstrap.registries(this.getRegistries());
            }
            bootstrap.initialize();
        }

        // 对默认配置进行检查,某些配置没有提供时,提供缺省值
        checkAndUpdateSubConfigs();
        // 初始化元数据的一些信息
        initServiceMetadata(provider);
        // 设置元数据信息,服务类型,服务引用
        serviceMetadata.setServiceType(getInterfaceClass());
        serviceMetadata.setTarget(getRef());
        serviceMetadata.generateServiceKey();
        if (!shouldExport()) {
            return;
        }
        // 是否异步导出
        if (shouldDelay()) {
            DELAY_EXPORT_EXECUTOR.schedule(() -> {
                try {
                    // Delay export server should print stack trace if there are exception occur.
                    this.doExport();
                } catch (Exception e) {
                    logger.error("delay export server occur exception, please check it.", e);
                }
            }, getDelay(), TimeUnit.MILLISECONDS);
        } else {
            // 导出服务
            doExport();
        }
        exported();
    }

doExport()内部的doExportUrls()方法,该方法才是真正的导出服务的核心。

private void doExportUrls() {
        // 所有的注册中心地址信息
        List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
        int protocolConfigNum = protocols.size();
        // 所有的协议,这里涉及到多协议多注册的情况
        for (ProtocolConfig protocolConfig : protocols) {
            String pathKey = URL.buildKey(getContextPath(protocolConfig)
                    .map(p -> p + "/" + path)
                    .orElse(path), group, version);
            // In case user specified path, register service one more time to map it to path.
            repository.registerService(pathKey, interfaceClass);
            // 暴露服务,这里会核心会创建Invoker和Exporter
            doExportUrlsFor1Protocol(protocolConfig, registryURLs, protocolConfigNum);
        }
    }

doExportUrlsFor1Protocol()方法。该方法内部核心就是创建一个可执行体Invoker对象和发布服务创建出Exporter对象。其中内部主要作用就是为一下几点:

1.本地服务导出:其内部根据URL 中Protocol类型为 injvm,会选择InjvmProtocol方法为exportLocal(url);

2.远程服务导出 & 有注册中心:其内部根据URL 中 Protocol 类型为 registry,会选择RegistryProtocol
3.远程服务导出 和没有注册中心:根据服务协议头类型判断,我们这里假设是 dubbo ,则会选择 DubboProtocol

 核心的部分也就是 RegistryProtocol#export 、 InjvmProtocol#export 、DubboProtocol#export 三个方法的执行过程。后续文章我将更加的细致的讲解其三个的暴露过程。

RegistryProtocol#export服务注册

DubboProtocol#export服务暴露,比如启动 了Netty 服务,让提供者获得网络通信的能力也在内部进行处理

InjvmProtocol#export本地服务暴露

doExportUrlsFor1Protocol()方法内部的核心代码部分:

Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass,
                                registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                        Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));

首先我们来讲解下Invoker,其实在得到Invoker对象,已经能够调用到我们对应的服务,它个人被我们理解成一个可执行体,最终不管rpc框架如何封装和远程调用发起到服务端,都是围绕Invoker对象来处理。因为他内部存储着我们真正的引用对象。值得讲解的一下是,上述代码获取到的invoker对象是经历过重重封装的。

扩展知识:其内部涉及到的设计模式有代理模式,责任链模式,装饰者模式,其中内部的拦截器也是在内部封装存储着,就使用到了责任链模式,内部多个拦截器形成了一个链条各司其职的拦截。当然装饰者模式在dubbo源码中随处可见,比如spi机制获取到的装载对象。

Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);

PROTOCOL.export(wrapperInvoker)方法是整个dubbo的核心可以这样理解,当前方法把我们的服务暴露到了我们的注册中心,和建立了网络通信,监听端口,注册监听器,处理服务调用端的请求。到了这里就完成了一个服务的暴露的完整过程,其实内部的实现细节相当多。这里我们只讲服务的整体暴露流程。

扩展知识:在阅读PROTOCOL.export(wrapperInvoker)方法时候一定要先理解dubbo的SPI机制,不然在这里会找不到头脑,我也是在读这部分的源码途中被绕来绕去,所以在熟悉SPI机制之后的运行原理才来读该部分源码,才能如鱼得水读懂该源码。

 该文章是我对自己看整个dubbo源码的一个梳理,如有什么有误的地方还请各位大佬指出。谢谢啦。整体执行流程如下:

dubbo请求到已经下线的服务_dubbo请求到已经下线的服务