Dubbo原理(3)--服务暴露流程

本文详细剖析了Dubbo服务暴露的流程,从ServiceBean在Spring容器中的初始化,到ServiceBean的afterPropertiesSet方法调用export(),再到doExport()、doExportUrls(),以及涉及到的注册中心、代理工厂和执行器的包装。最终,DubboProtocol通过启动Netty服务器监听端口,并通过RegistryProtocol在注册中心注册服务,完成服务的暴露。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

暴露服务时序:原图地址:http://dubbo.apache.org/zh-cn/docs/dev/design.html

/dev-guide/images/dubbo-export.jpg


解析service标签,会将service标签封装成ServiceBean:


ServiceBean怎么做:

public class ServiceBean<T> extends ServiceConfig<T> 
implements InitializingBean, DisposableBean, ApplicationContextAware, 
ApplicationListener<ContextRefreshedEvent>, BeanNameAware {...}

1.ServiceBean实现了InitializingBean:

  • InitializingBean是Spring里面的一个重要接口,当组件创建完对象之后,会调用InitializingBean;
  • 在属性设置完之后,会调用afterPropertiesSet方法:

2.ServiceBean实现了ApplicationListener:

  • 应用的监听器,Spring原理里面核心的一部分;
  • ApplicationListener监听的事件是:ContextRefreshedEvent;
  • 当IOC容器刷新完成/IOC容器里面所有对象创建完,会回调ApplicationListener里面的onApplicationEvent方法;

3.ServiceBean在容器创建完对象以后,调用方法afterPropertiesSet;

还会在IOC容器启动完成后,调用onApplicationEvent;

public class ServiceBean<T> extends ServiceConfig<T> 
implements InitializingBean, DisposableBean, ApplicationContextAware, 
ApplicationListener<ContextRefreshedEvent>, BeanNameAware {
	...
	@Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ...
    }
	...
	@Override
    @SuppressWarnings({"unchecked", "deprecation"})
    public void afterPropertiesSet() throws Exception {
        ...
    }
	...
}

ServiceBean的afterPropertiesSet方法里面有如下内容:

public void afterPropertiesSet() throws Exception {
    ...
    setProvider(providerConfig);
    ...
    setApplication(applicationConfig);
    ...
    setModule(moduleConfig);
    ...
    super.setRegistries(registryConfigs);
    ...
    setMonitor(monitorConfig);
    ...
    super.setProtocols(protocolConfigs);
    ...
}
setProvider(providerConfig):是将标签里面配的provider信息保存起来,保存到当前的ServiceBean里面;setApplication(applicationConfig);setModule(moduleConfig);super.setRegistries(registryConfigs);setMonitor(monitorConfig);super.setProtocols(protocolConfigs);方法则类似;

afterPropertiesSet方法:相当于将以前已经配置的信息,都保存起来;

下一步回调了:onApplicationEvent;


ServiceBean的onApplicationEvent方法里面有如下内容:

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

当IOC容器:ContextRefreshedEvent event,刷新完成之后,调一个方法:

if (isDelay() && !isExported() && !isUnexported()) :

  • 如果不是延迟暴露,而且还没暴露,而且还不是不暴露:既要暴露,还没暴露,而且还不是延迟暴露,就会调用export()方法;

  • export();方法用于暴露服务;

相关流程图如下:


export()如何暴露?

export()方法:为ServiceConfig类里面的export()方法:

public class ServiceConfig<T> extends AbstractServiceConfig {
    ...
    private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
    ...
    public synchronized void export() {
        ...

        if (delay != null && delay > 0) {
            delayExportExecutor.schedule(new Runnable() {
                @Override
                public void run() {
                    doExport();
                }
            }, delay, TimeUnit.MILLISECONDS);
        } else {
            doExport();
        }
    }
    ...
    protected synchronized void doExport() {
        ...
        doExportUrls();
        ...
    }
    ...
    private void doExportUrls() {
        List<URL> registryURLs = loadRegistries(true);
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }
    ...
    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        ...
             Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
             DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

             Exporter<?> exporter = protocol.export(wrapperInvoker);
             exporters.add(exporter);
        ...
    }
}

export()方法的关键一步:doExport(),也就是:执行暴露;

doExport()方法的关键一步:doExportUrls(),也就是:执行暴露url地址;

doExportUrls():

  • 第一步:加载注册中心的信息,读取注册中心的地址;
  • 第二步:暴露服务。如果要暴露在多个端口(for循环),可以配置多个<dubbo:protocol...>的标签。protocol:协议,可以使用dubbo协议等等,也可以使用其他协议去暴露;
  • 调用doExportUrlsFor1Protocol(..., ...)方法;

doExportUrlsFor1Protocol(..., ...):

  • 于此同时,观察注册中心,方法什么时候被注册到注册中心,是可以被看见的;
  • 有一些重要的方法:proxyFactory(代理工厂),要获取一些invoker(执行器)。这个invoker实际上是将暴露的服务包装了一下:服务的实现、服务的地址等等,组合成了一个invoker;
  • wrapperInvoker:对执行器再次进行包装;
  • 包装完成后,会调用protocol.export(...)方法暴露invoker,一暴露就会生成exporter,exporter会被保存起来;
  • protocol是通过这种方式得到的(详见代码):Protocol.class获取扩展的类加载器,得到它的适配。我们会得到protocol接口里面当前要用的(Protocol有多个实现);
  • 由于使用Dubbo协议进行暴露,于是使用DubboProtocol;我们也会把它注册到注册中心,于是使用RegistryProtocol;
  • DubboProtocol和RegistryProtocol都有一个方法export(),暴露是如何进行的?
  • 会先来到RegistryProtocol的export()方法,先来到注册中心来暴露;

public class RegistryProtocol implements Protocol {
    ...
    @Override
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        //export invoker
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
        ...
        ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);

        if (register) {
            register(registryUrl, registedProviderUrl);
            ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
        }

        ...
        return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl);
    }
    ...
    private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) {
        ...
        ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
        if (exporter == null) {
            synchronized (bounds) {
                exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
                if (exporter == null) {
                    final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
                    exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
                    bounds.put(key, exporter);
                }
            }
        }
        return exporter;
    }
}
  • RegistryProtocol的export()方法:第一步:originInvoker是原生的执行者,封装了服务的实现、接口和url地址。doLocalExport来做本地的暴露;第二步:ProviderConsumerRegTable(提供者和消费者的注册表),注册表里面注册提供者,注册就是把提供者、注册中心地址、提供者的url地址传进来;
  • doLocalExport方法:第一步:做一个暴露器:exporter,拿到要暴露的执行者,调用protocol(DubboProtocol)进行暴露(使用Dubbo协议进行服务的暴露);

public class DubboProtocol extends AbstractProtocol {
    ...
    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        URL url = invoker.getUrl();

        // export service.
        String key = serviceKey(url);
        DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
        exporterMap.put(key, exporter);
        ...
        openServer(url);
        ...
        return exporter;
    }
    private void openServer(URL url) {
        ...
        String key = url.getAddress();
        ...
        if (isServer) {
            ExchangeServer server = serverMap.get(key);
            if (server == null) {
                serverMap.put(key, createServer(url));
            } else {
                // server supports reset, use together with override
                server.reset(url);
            }
        }
    }
    private ExchangeServer createServer(URL url) {
        ...
        ExchangeServer server;
        try {
            server = Exchangers.bind(url, requestHandler);
        } catch (RemotingException e) {
            ...
        }
        ...
        return server;
    }
    ...
}
  • 先拿到url地址,相当于我们要在注册中心里面调用的地址。把地址各种转化,包装成DubboExporter(暴露器);
  • DubboProtocol会做一件事:openServer(打开服务器);
  • openServer会先拿到地址(服务暴露的端口),ExchangeServer会调用createServer来创建一个信息交换的服务器(如果server为空);
  • createServer方法会创建一个服务器(ExchangeServer),调用Exchangers.bind(..., ...)方法来绑定服务器和请求的处理器。这个bind其实是Transporters(传输器)来进行bind,Transporters进行的bind就会进入NettyTransporter的底层了(启动netty服务器等);
  • 所以,当我们openServer,先是创建服务器时,其实就是启动netty服务器,监听某一端口(eg:20880);

RegistryProtocol的doLocalExport(...)方法的下面:

ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl):是ProviderConsumerRegTable注册提供者;

public class ProviderConsumerRegTable {
    public static ConcurrentHashMap<String, Set<ProviderInvokerWrapper>> providerInvokers = new ConcurrentHashMap<String, Set<ProviderInvokerWrapper>>();
    public static ConcurrentHashMap<String, Set<ConsumerInvokerWrapper>> consumerInvokers = new ConcurrentHashMap<String, Set<ConsumerInvokerWrapper>>();

    public static void registerProvider(Invoker invoker, URL registryUrl, URL providerUrl) {
        ProviderInvokerWrapper wrapperInvoker = new ProviderInvokerWrapper(invoker, registryUrl, providerUrl);
        String serviceUniqueName = providerUrl.getServiceKey();
        Set<ProviderInvokerWrapper> invokers = providerInvokers.get(serviceUniqueName);
        if (invokers == null) {
            providerInvokers.putIfAbsent(serviceUniqueName, new ConcurrentHashSet<ProviderInvokerWrapper>());
            invokers = providerInvokers.get(serviceUniqueName);
        }
        invokers.add(wrapperInvoker);
    }
    ...
}
  • 这个注册其实是保存一些信息:providerInvokers(服务提供者的执行者),与此同时,有consumerInvokers。
  • providerInvokers和consumerInvokers保存:url地址,和对应的服务提供者/消费者的执行器;
  • 执行器里面有真正的服务的对象的实现等等,把这种管理关系(要暴露的服务和地址)维护在这里,至此,服务暴露;

服务暴露的两步:

  1. DubboProtocol会启动Netty的服务器,监听某一端口(eg:20880);
  2. registry会帮我们注册服务,把服务的地址保存到注册中心,每一个url地址的调用会来保存它实际的invoker(执行器);

当我们暴露服务的时候:

  1. 我们要获取到invoker:getInvoker()(获取到执行器);
  2. 我们使用Protocol来暴露执行器;
  3. 而Protocol会使用两个,一个是DubboProtocol,一个是RegistryProtocol,这两个protocol对应两个Exporter(暴露者);
  4. DubboProtocol:开启服务器;
  5. RegistryProtocol:把:每一个服务和对应的url地址信息,都保存在注册表(ProviderConsumerRegTable)里面。注册表里面缓存了:一个url地址,和对应的执行器。执行器里面有真正的服务。通过url地址,我们可以拿到执行器,就可以完成服务的调用了;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值