3、Dubbo源码系列-服务发布

229 阅读3分钟

上篇文章,介绍了Dubbo增强SPI与动态增强原理,用来当作学习Dubbo源码的“开胃菜”。作为RPC框架,如何实现服务的发布和调用才是重中之重,本篇文章将会带大家一起学习下远程服务发布的过程。

一、Demo

public class Application {
    public static void main(String[] args) throws Exception {
        ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>();
        service.setInterface(DemoService.class);
        service.setRef(new DemoServiceImpl());
        DubboBootstrap bootstrap = DubboBootstrap.getInstance();
        bootstrap
                .application(new ApplicationConfig("dubbo-demo-api-provider"))
                .registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))
                .service(service)
                .start()
                .await();
    }
}

Dubbo提供了多种服务暴露方式,本文以API的方式着手分析,更简单明了一些。

二、时序图

image.png 以上,则是服务注册到注册中心的全过程。

三、步骤详解

1.步骤1-start

首先通过DubboBootstrap.start方法,触发服务初始化,可以看到其内部是调用了exportServices方法来进行服务的暴露

    public DubboBootstrap start() {
        if (started.compareAndSet(false, true)) {
            initialize();
            // 1. export Dubbo Services
            exportServices();
            // Not only provider register, TODO
            if (!isOnlyRegisterProvider() || hasExportedServices()) {
                // 2. export MetadataService
                exportMetadataService();
                //3. Register the local ServiceInstance if required
                registerServiceInstance();
            }
            // 步骤17完成服务引用,留到客户端流程讲解时再分析
            referServices();
        }
        return this;
    }

2.步骤2-exportServices

exportServices方法,从configManager里获取到需要暴露的服务,循环处理每一个ServiceConfig,然后调用其export方法,同时判断当前是否需要异步export,进而选择不同的处理方式。

    private void exportServices() {
        configManager.getServices().forEach(sc -> {
            // TODO, compatible with ServiceConfig.export()
            ServiceConfig serviceConfig = (ServiceConfig) sc;
            serviceConfig.setBootstrap(this);

            if (exportAsync) {
                ExecutorService executor = executorRepository.getServiceExporterExecutor();
                Future<?> future = executor.submit(() -> {
                    sc.export();
                });
                asyncExportingFutures.add(future);
            } else {
                sc.export();
                exportedServices.add(sc);
            }
        });
    }

3.步骤3-export

ServiceConfig的export方法支持延迟暴露rpc服务,个人理解有的rpc服务可能依赖一些前置的数据处理,比如要load一些数据到内存里,需要一定的准备时间,服务启动的时候,不能立刻对外提供服务,就需要延迟去暴露自己的rpc服务。可以通过ServiceConfig的setDelay方法设置延迟发布时间。没有设置延迟时间,则直接调用doExport

    public synchronized void export() {
        ... ...
        if (shouldDelay()) {
            DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
        } else {
            // 执行export动作
            doExport();
        }
    }

4.步骤4-doExport

doExport方法内部,调用了doExportUrls进行服务的发布。发布完成后,调用dispatch发布了export事件。

    protected synchronized void doExport() {
        ... ...
        doExportUrls();
        // dispatch a ServiceConfigExportedEvent since 2.7.4
        // 步骤15
        dispatch(new ServiceConfigExportedEvent(this));
    }

该事件,最终被ServiceNameMappingListener处理,调用ServiceNameMapping.map方法。

public class ServiceNameMappingListener implements EventListener<ServiceConfigExportedEvent> {
    private final ServiceNameMapping serviceNameMapping = getDefaultExtension();
    @Override
    public void onEvent(ServiceConfigExportedEvent event) {
        ServiceConfig serviceConfig = event.getServiceConfig();
        List<URL> exportedURLs = serviceConfig.getExportedUrls();
        exportedURLs.forEach(url -> {
            ... ...
            // 步骤16
            serviceNameMapping.map(serviceInterface, group, version, protocol);
        });
    }
}

查看ServiceNameMapping默认实现DynamicConfigurationServiceNameMapping发现,其内部是调用DynamicConfiguration的publishConfig方法,实现config的发布。DynamicConfiguration实现如下 image.png 可以看到,不同的注册中心对应不同的实现类。

5.步骤5-doExportUrls

loadRegistries方法用来加载所有的服务注册中心对象,在Dubbo中,一个服务可以被注册到多个注册中心。遍历所有协议,进行服务发布

    private void doExportUrls() {

        List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);

        for (ProtocolConfig protocolConfig : protocols) {
            String pathKey = URL.buildKey(getContextPath(protocolConfig)
                    .map(p -> p + "/" + path)
                    .orElse(path), group, version);
            repository.registerService(pathKey, interfaceClass);
            serviceMetadata.setServiceKey(pathKey);
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

6.步骤6-doExportUrlsFor1Protocol

代码有点长,就不贴了,简单总结下流程:

  • 解析MethodConfig配置
  • 如果为泛型调用,则设置为泛型类型
  • 不是泛型调用,则获取拼接URL的参数
  • 拼接URl对象
  • 导出服务,Dubbo导出分为本地导出和远程导出,本地导出使用了inJvm协议,不开启端口,不发起远程调用,只在jvm内部进行关联。

最终调用protocol.export方法进行服务的暴露,前文讲解SPI的时候分析过,此时的protocol实际为Protocol$Adaptive类,内部会根据服务的类型选择使用InJvmProtocol还是DubboProtocol,但由于Dubbo扩展点都使用了Wrapper增强,因此需要经历层层调用才会调用到RegistryProtocol的export方法。

7.步骤13-export

    @Override
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        ... ...
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

        final Registry registry = getRegistry(originInvoker);
        final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
        boolean register = providerUrl.getParameter(REGISTER_KEY, true);
        if (register) {
            register(registryUrl, registeredProviderUrl);
        }

        // Deprecated! Subscribe to override rules in 2.6.x or before.
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);

        exporter.setRegisterUrl(registeredProviderUrl);
        exporter.setSubscribeUrl(overrideSubscribeUrl);
        //Ensure that a new exporter instance is returned every time export
        return new DestroyableExporter<>(exporter);
    }

doLocalExport方法最终启动了NettyServer进行服务监听。服务启动后,调用register方法,把服务相关信息注册到对应的注册中心。

四、小节

由于Dubbo服务暴露的流程比较长,本文主要分析了Dubbo服务启动到Netty启动前的主要流程,由于篇幅限制,很多细节都没展开讲,如步骤17refreshServce方法、Dubbo的event处理机制、注册中心实现等等,心急吃不了热豆腐,后续会逐渐为大家一一详解。大家如果有什么疑问也可以在评论区进行讨论交流。