Dubbo源码——服务的创建和暴露

Posted TechHeaven

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dubbo源码——服务的创建和暴露相关的知识,希望对你有一定的参考价值。

前言

本文主要就dubbo基础的XML解析开始,到服务的创建和发布,最终延展到底层netty的调用,给出一个初始化流程的解读。

在这里你能看到dubbo一些优秀的抽象设计,以及底层源代码的解读。如有语义不明,请直接在公招号内联系我即可。


service和server

首先,当发布一个服务的Server时,我们需要编写一个Spring配置文件,形如以下:


<beans xmlns="...">

    <dubbo:application name="hello-world-app" />

    <dubbo:protocol name="dubbo" port="20880" />

    <dubbo:service interface="net.beamlight.dubbo.service.DemoService" ref="demoService" registry="..." />

    <bean id="demoService" class="net.beamlight.dubbo.provider.DemoServiceImpl" />

</beans>


其中关键的一行为<dubbo:service …>,这里使用了扩展的Spring Schema,相关定义在jar包META-INF目录下的spring.handlers、spring.schemas、dubbo.xsd中。解析器为com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler,所以它也就成了启动provider的“应用程序入口”。


而我们观察这个NamespaceHandler的代码,非常的简短,这里就不贴了。实际就重写了NamespaceHandlerSupport的init方法,注册了一些BeanDefinitionParser,而读过spring源码的人都应该了解,spring的启动流程:先根据xml生成beanDefinition,然后再根据beanDefinition生成Bean并放到一个concurentHashMap中。那么我们直接看DubboNamespaceHandler中注册的DubboBeanDefinitionParser,这里有这么一行代码:


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


DubboBeanDefinitionParser中主要做了一些BeanDefinition的初始化,我们跳过直接看ServiceBean

同样我们省略一些初始化(这里其实是初始化了一些config,比如protocolConfig、monitorConfig等等,具体细节查看源码),在初始化的最后我们找到了这么几行代码:


/* 省略一些初始化操作 */

if (!isDelay()) {

export();

}


export即为我们的发布操作,二话不说点进去。

export是ServiceConfig中的方法,而ServiceBean继承自ServiceConfig(这个地方的抽象是不是有点不太合适?思考中...),在export中我们看到了这么几个现象:


1. delay的使用:

if (delay != null && delay > 0) {

Thread thread = new Thread(new Runnable() {

public void run() {

try {

Thread.sleep(delay);

} catch (Throwable e) {

//我们看到这里是直接ignore掉了

}

doExport();

}

});

thread.setDaemon(true);

thread.setName("DelayExportServiceThread");

thread.start();


我们发现,delay的作用就是延迟暴露,而延迟的方式也很直截了当,Thread.sleep(delay),另外一个比较有意思的就是我们可以看到,延迟暴露的服务是通过守护线程发布的,而守护线程的优先级比较低,这里的设计是不是有何用意,有兴趣的同学可以细究一下或者有知道的告诉我一下。


2. export是synchronized修饰的方法。也就是说暴露的过程是原子操作,正常情况下不会出现锁竞争的问题,毕竟初始化过程大多数情况下都是单一线程操作,这里联想到了spring的初始化流程,也进行了加锁操作,这里也给我们平时设计一个不错的启示:初始化流程的性能调优优先级应该放的比较低,但是安全的优先级应该放的比较高


继续看doExport()方法。同样是一堆初始化代码,这里我们看到了如果provider已经存在(provider != null)的情况下,我们之前说过的初始化的monitorConfig、moudleConfig中的一些配置是不会生效的(protocol、monitor等属性),而是直接从已有的provider中获取的,而provider的初始化是在serviceBean中,根据:


//如果上下文已经存在的情况下,从上下文中获取providers

Map<String, ProviderConfig> providerConfigMap =  applicationContext == null ? null  : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class, false, false);


...


ProviderConfig providerConfig = null;

for (ProviderConfig config : providerConfigMap.values()) {

if (config.isDefault() == null || config.isDefault().booleanValue()) {

if (providerConfig != null) {

throw new IllegalStateException("Duplicate provider configs: " + providerConfig + " and " + config);

}

providerConfig = config;

}

}

if (providerConfig != null) {

//这里的setProvider方法继承自ServiceConfig

setProvider(providerConfig);

}


获得。


export过程

继续看doExport(),最终会调用到doExportUrls()中:


//DUBBO框架整体是基于URL为总线的方式来达到各个模块之间的动态加载,这里也有体现,后面我们在dubbo的插件化中会特别讲解

List<URL> registryURLs = loadRegistries(true);

for (ProtocolConfig protocolConfig : protocols) {

doExportUrlsFor1Protocol(protocolConfig, registryURLs);

}


继续看doExportUrlsFor1Protocol()方法之前,我们会发现,实际上发布服务的是protocol,这里的抽象做的也很精彩,毕竟各个protocol的内容大不相同,protocol后面还会讲解,默认的protocol是dubboProtol。在doExportUrlsFor1Protocol中,我们会看到如下语句:


...

Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class)interfaceClass, url);

Exporter<?> exporter = protocol.export(invoker);

exporters.add(exporter);

...


这里我们会发现:

1. dubbo的比较核心的抽象之一:Invoker

2. invoke实际上是一个代理类,从proxyFactory(默认为javassist的proxyFactory)中生成。


这里我们也做一个小总结,各个dubbo组件的作用:

  • Invoker – 执行具体的远程调用

  • Exporter – 暴露服务或取消暴露



protocol发布服务

我们看一下dubboProtocol的export方法:


...

openServer(url);

...


省略一些步骤和判断,直接看openServer:


...

ExchangeServer server = serverMap.get(key);

if (server == null) {

serverMap.put(key, createServer(url));

} else {

//server支持reset,配合override功能使用

server.reset(url);

}


继续看其中的createServer方法:


//requestHandler包含通信协议的主要处理逻辑

private ExchangeHandler requestHandler = new ExchangeHandlerAdapter(){

...

};

//do sth...

ExchangeServer server;

try {

server = Exchangers.bind(url, requestHandler);

} catch (RemotingException e) {

throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);

}


发现ExchangeServer是通过Exchangers创建的,直接看bind方法:


...

getExchanger(url).bind(url, handler);

...


getExchanger方法实际上调用的是ExtensionLoader的相关方法,这里的ExtensionLoader是dubbo插件化的核心,我们会在后面的插件化讲解中详细讲解,这里我们只需要知道Exchanger的默认实现只有一个:HeaderExchanger。上面一段代码最终调用的是:


public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {

return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));

}


可以看到Server与Client实例均是在这里创建的,HeaderExchangeServer需要一个Server类型的参数,来自Transporters.bind():


public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {

...

return getTransporter().bind(url, handler);

}


getTransporter()获取的实例来源于配置,默认返回一个NettyTransporter:


public Server bind(URL url, ChannelHandler listener) throws RemotingException {

return new NettyServer(url, listener);

}


最终来到了NettyServer,在它的doOpen()方法中看到了我们熟悉的Netty Bootstrap~~,至此,我们一个provider的注册完成,可以看到底层dubbo的默认实现时netty。


以上是关于Dubbo源码——服务的创建和暴露的主要内容,如果未能解决你的问题,请参考以下文章

Dubbo原理何源码解析之服务暴露

Dubbo源码解析:服务暴露与发现

dubbo源码学习dubbo暴露服务的过程

dubbo源码分析 之 服务本地暴露

Dubbo源码阅读系列服务暴露之远程暴露

dubbo学习-服务暴露与注册源码剖析