Dubbo源码分析之服务暴露

Dubbo调用过程参与者有服务提供方、注册中心、服务消费方。其中注册中心是单独部署的,服务提供方和消费方是集成在业务里面的,今天来分析下服务提供方服务暴露的流程。
不管通过哪种启动方式(Dubbo的几种启动方式),服务接口暴露的起点都是从ServiceConfig.export方法开始的,这里先简单分析下通过xml中配置dubbo到export的流程。

从xml配置到ServiceConfig.export

xml中dubbo服务提供方的配置:





可以看到,配置文件中使用的是dubbo的自定义标签,那么,就先来看dubbo的自定义标签解析器。通过dubbo的spring.handlers文件,知道dubbo标签是通过
DubboNamespaceHandler来处理的:

registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));

可以看到解析service标签委托给了DubboBeanDefinitionParser,beanClass为ServiceBean,继续看DubboBeanDefinitionParser.parse方法:

private static BeanDefinition parse(Element element, ParserContext parserContext, Class beanClass, boolean required) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(beanClass);
        beanDefinition.setLazyInit(false);
...
        parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
...

parse的代码做了删减,主要的逻辑是配置bean的属性并往spring容器中注册了beanClass的beanDefinition,此时是ServiceBean。所以通过xml进行配置dubbo其实主要是在完成一件事情,就是配置好ServiceBean的属性,然后将它注册到spring容器中。而ServiceBean是ServiceConfig的子类,同时实现了InitializingBean、ApplicationContextAware、ApplicationListener等spring扩展功能接口。根据ApplicationContext是否支持事件监听机制来决定是在收到ContextRefreshedEvent时调用export还是初始化的时候进行:

@Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (!isExported() && !isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            export();
        }
    }
public void afterPropertiesSet() throws Exception {
       ......
       if (!supportedApplicationListener) {
            export();
        }
    }

ServiceBean的export中调用了super.export,也就是ServiceConfig.export方法。

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

上面流程分析了xml中配置dubbo也是通过ServiceConfig.export方法来暴露服务的,下面从ServiceConfig.export来分析服务启动的过程。

ServiceConfig.export服务暴露流程分析

首先从需求角度来大至服务暴露需要完成哪些工作?
服务暴露成功后,消费端就可以通过接口透明的调用服务了,由于是RPC调用,是通过网络访问来完成的,所以服务暴露肯定需要创建一个网络服务器等待消费者来连接通信;要进行通信肯定需要定义协议,才能保证双方能够正常的通信;消费方并不是直接写死的服务提供方主机和端口,而是从注册中心拉取的,这样才灵活可扩展,所以服务暴露的过程肯定需要将自己的信息注册到注册中心。下面就从ServiceConfig.export源码开始看下这个过程

public synchronized void export() {
        checkAndUpdateSubConfigs();

        if (!shouldExport()) {
            return;
        }

        if (shouldDelay()) {
            DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
        } else {
            doExport();
        }
    }

做了一些校验和延时暴露的判断,关键流程在doExport中:

protected synchronized void doExport() {
        if (unexported) {
            throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
        }
        if (exported) {
            return;
        }
        exported = true;

        if (StringUtils.isEmpty(path)) {
            path = interfaceName;
        }
        doExportUrls();
    }

通过标识位设置避免重复暴露,然后进入doExportUrls:

private void doExportUrls() {
        List registryURLs = loadRegistries(true);
        for (ProtocolConfig protocolConfig : protocols) {
            String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
            ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
            ApplicationModel.initProviderModel(pathKey, providerModel);
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

迭代针对每一种配置的协议调用doExportUrlsFor1Protocol进行暴露(一个接口可以通过多种协议同时进行暴露),这个方法里面开始了上述逻辑,方法体比较长,节选几个比较关键的部分:

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
        //省略一些参数配置的部分
        // export service
        String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
        Integer port = this.findConfigedPorts(protocolConfig, name, map);
        //将协议、接口、主机、端口和配置参数封装到URL对象里面
        URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
        ...
        //下面是服务暴露的关键代码
        Invoker invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
        Exporter exporter = protocol.export(wrapperInvoker);
        exporters.add(exporter);
}

最后几行是服务暴露的关键代码,着重看下。

  • PROXY_FACTORY.getInvoker是将服务的接口的实现类封装成Invoker, Invoker是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。当远程请求过来是,就是通过这个invoker来调用服务端接口实现类的具体方法的。
  • protocol.export(wrapperInvoker)是通过协议将invoker进行暴露,是最关键的地方了。接下来跟进这个方法里面详细查看,由于protocol是一个自适应拓展点,而且Protocol接口没有默认的自适应实现类(有关拓展点可查看Dubbo拓展点),所以protocol.export的执行是根据URL中protocol参数来决定的。
    在有注册中心的情况下(没有注册中心的情况比较简单,且包含在了有注册中心的情况下),invoker中的url是registryURL,协议参数为registry,所以export方法调用的是RegistryProtocol. export:
@Override
    public  Exporter export(final Invoker originInvoker) throws RpcException {
        URL registryUrl = getRegistryUrl(originInvoker);
        // url to export locally
        URL providerUrl = getProviderUrl(originInvoker);

        // Subscribe the override data
        // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call
        //  the same service. Because the subscribed is cached key with the name of the service, it causes the
        //  subscription information to cover.
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);

        providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
        //export invoker
        final ExporterChangeableWrapper exporter = doLocalExport(originInvoker, providerUrl);

        // url to registry
        final Registry registry = getRegistry(originInvoker);
        final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
        ProviderInvokerWrapper providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
                registryUrl, registeredProviderUrl);
        //to judge if we need to delay publish
        boolean register = registeredProviderUrl.getParameter("register", true);
        if (register) {
            register(registryUrl, registeredProviderUrl);
            providerInvokerWrapper.setReg(true);
        }

        // 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(originInvoker, providerUrl)是本地暴露,而register(registryUrl, registeredProviderUrl)则是将本地暴露的URL注册到注册中心了。

private  ExporterChangeableWrapper doLocalExport(final Invoker originInvoker, URL providerUrl) {
        String key = getCacheKey(originInvoker);

        return (ExporterChangeableWrapper) bounds.computeIfAbsent(key, s -> {
            Invoker invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
            return new ExporterChangeableWrapper<>((Exporter) protocol.export(invokerDelegate), originInvoker);
        });
    }

本地暴露的时候,也是调用的protocol.export,不过此时的invoker中的URL是providerURL,协议参数为具体的协议,默认是dubbo,会进入dubboProtocol.export方法,如果没有配置注册中心的话,也是直接进入这个方法:

@Override
    public  Exporter export(Invoker invoker) throws RpcException {
        URL url = invoker.getUrl();

        // export service.
        String key = serviceKey(url);
        DubboExporter exporter = new DubboExporter(invoker, key, exporterMap);
        exporterMap.put(key, exporter);

        //export an stub service for dispatching event
        Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT);
        Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false);
        if (isStubSupportEvent && !isCallbackservice) {
            String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY);
            if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
                if (logger.isWarnEnabled()) {
                    logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) +
                            "], has set stubproxy support event ,but no stub methods founded."));
                }

            } else {
                stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
            }
        }

        openServer(url);
        optimizeSerialization(url);

        return exporter;
    }

看到openServer(url)猜测应该就是开启服务器,监听远程网络连接了,源码就不一一列举了,简单理解就是开启了NettyServer并注册了requestHandler来处理入站数据。
这样本地暴露就完成了,结果是创建了一个netty服务器等待远程连接来访问,这个时候已经可以通过消费端来调用接口了,但是写死的服务端ip、端口不够灵活,所以一般都使用注册中心来进行服务发现。回过头来看服务注册到注册中心的流程:register(registryUrl, registeredProviderUrl):

public void register(URL registryUrl, URL registeredProviderUrl) {
        Registry registry = registryFactory.getRegistry(registryUrl);
        registry.register(registeredProviderUrl);
    }

实际上就是通过工厂方法获取注册中心,然后注册服务提供方的URL,注册中心有多种实现,以zookeeper为例,

@Override
    public void doRegister(URL url) {
        try {
            zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
        } catch (Throwable e) {
            throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }

实现为向zookeeper根据接口参数创建一个临时目录,这样在消费端连接上注册中心后,就可以从对应的接口目录下获取provider的信息了。

你可能感兴趣的:(Dubbo源码分析之服务暴露)