欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

面试必备:Dubbo中暴露服务的过程解析

程序员文章站 2022-04-11 17:50:10
...

面试必备:Dubbo中暴露服务的过程解析

 

Dubbo会在Spring实例化完bean之后,在刷新容器最后一步发布ContextRefreshEvent事件的时候,通知实现了ApplicationListener的ServiceBean类进行回调onApplicationEvent 事件方法,dubbo会在这个方法中调用ServiceBean父类ServiceConfig的export方法,而该方法真正实现了服务的(异步或者非异步)发布。

加载dubbo配置

Spring容器在启动的时候,会读取到Spring默认的一些schema以及Dubbo自定义的schema,每个schema都会对应一个自己的NamespaceHandler,NamespaceHandler里面通过BeanDefinitionParser来解析配置信息并转化为需要加载的bean对象。

遇到dubbo名称空间 ,首先会调用DubboNamespaceHandler类的 init方法 进行初始化操作。

根据命名空间去获取具体的处理器NamespaceHandler。那具体的处理器是在哪定义的呢,在” META-INF/spring.handlers”文件中,Spring在会自动加载该文件中所有内容。

META-INF/spring.handlers

http\:
//code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler

META-INF/spring.schemas

http\:
//code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd

根据不同的XML节点,会委托NamespaceHandlerSupport 类找出合适的BeanDefinitionParser,其中Dubbo所有的标签都使用 DubboBeanDefinitionParser进行解析,基于一对一属性映射,将XML标签解析为Bean对象。

DubboNamespaceHandler.java 类代码如下

package com.alibaba.dubbo.config.spring.schema;


import
 org.springframework.beans.factory.xml.
NamespaceHandlerSupport
;


public
 
class
 
DubboNamespaceHandler
 
extends
 
NamespaceHandlerSupport
 {


    
static
 {

        
Version
.checkDuplicate(
DubboNamespaceHandler
.
class
);

    }


    
public
 
void
 init() {

        registerBeanDefinitionParser(
"application"
, 
new
 
DubboBeanDefinitionParser
(
ApplicationConfig
.
class
, 
true
));

        registerBeanDefinitionParser(
"module"
, 
new
 
DubboBeanDefinitionParser
(
ModuleConfig
.
class
, 
true
));

        registerBeanDefinitionParser(
"registry"
, 
new
 
DubboBeanDefinitionParser
(
RegistryConfig
.
class
, 
true
));

        registerBeanDefinitionParser(
"monitor"
, 
new
 
DubboBeanDefinitionParser
(
MonitorConfig
.
class
, 
true
));

        registerBeanDefinitionParser(
"provider"
, 
new
 
DubboBeanDefinitionParser
(
ProviderConfig
.
class
, 
true
));

        registerBeanDefinitionParser(
"consumer"
, 
new
 
DubboBeanDefinitionParser
(
ConsumerConfig
.
class
, 
true
));

        registerBeanDefinitionParser(
"protocol"
, 
new
 
DubboBeanDefinitionParser
(
ProtocolConfig
.
class
, 
true
));

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

        registerBeanDefinitionParser(
"reference"
, 
new
 
DubboBeanDefinitionParser
(
ReferenceBean
.
class
, 
false
));

        registerBeanDefinitionParser(
"annotation"
, 
new
 
DubboBeanDefinitionParser
(
AnnotationBean
.
class
, 
true
));

    }


}

由于DubboBeanDefinitionParser 类中 parse转换的过程代码还是比较复杂,只抽离出来bean的注册这一块的代码如下

DubboBeanDefinitionParser.java 类代码如下

package com.alibaba.dubbo.config.spring.schema;


public
 
class
 
DubboBeanDefinitionParser
 
implements
 
BeanDefinitionParser
 {


    
@SuppressWarnings
(
"unchecked"
)

    
private
 
static
 
BeanDefinition
 parse(
Element
 element, 
ParserContext
 parserContext, 
Class
<?> beanClass, 
boolean
 required) {


    
RootBeanDefinition
 beanDefinition = 
new
 
RootBeanDefinition
();

        beanDefinition.setBeanClass(beanClass);

        beanDefinition.setLazyInit(
false
);

        
String
 id = element.getAttribute(
"id"
);

        
//省略......

         
if
(id != 
null
 && id.length() > 
0
) {

            
if
(parserContext.getRegistry().containsBeanDefinition(id)) {

                
throw
 
new
 
IllegalStateException
(
"Duplicate spring bean id "
 + id);

            }

            
//registerBeanDefinition 注册Bean的定义

            
//具体的id如下 applicationProvider.xml解析后的显示 id,

            
//如id="dubbo_provider"  beanDefinition = "ApplicationConfig"

            parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);

            beanDefinition.getPropertyValues().addPropertyValue(
"id"
, id);

        }

     }  

}

通过DubboBeanDefinitionParser 类的 parse方法会将class信息封装成BeanDefinition,然后将BeanDefinition再放进DefaultListableBeanFactory的beanDefinitionMap中。

最后通过Spring bean 的加载机制进行加载。

服务暴露过程

Dubbo会在Spring实例化完bean之后,在刷新容器最后一步发布ContextRefreshEvent事件的时候,通知实现了ApplicationListener的ServiceBean类进行回调onApplicationEvent 事件方法,dubbo会在这个方法中调用ServiceBean父类ServiceConfig的export方法,而该方法真正实现了服务的(异步或者非异步)发布。

服务暴露入口

由服务配置类 ServiceConfig 进行初始化工作及服务暴露入口,首先进去执行该类的export()方法。

ServiceConfig.java 类的 export 方法

export的步骤简介

  1. 首先会检查各种配置信息,填充各种属性,总之就是保证我在开始暴露服务之前,所有的东西都准备好了,并且是正确的。

  2. 加载所有的注册中心,因为我们暴露服务需要注册到注册中心中去。

  3. 根据配置的所有协议和注册中心url分别进行导出。

  4. 进行导出的时候,又是一波属性的获取设置检查等操作。

  5. 如果配置的不是remote,则做本地导出。

  6. 如果配置的不是local,则暴露为远程服务。

  7. 不管是本地还是远程服务暴露,首先都会获取Invoker。

  8. 获取完Invoker之后,转换成对外的Exporter,缓存起来。

export方法先判断是否需要延迟暴露(这里我们使用的是不延迟暴露),然后执行doExport方法。

doExport方法先执行一系列的检查方法,然后调用doExportUrls方法。检查方法会检测dubbo的配置是否在Spring配置文件中声明,没有的话读取properties文件初始化。

doExportUrls方法先调用loadRegistries获取所有的注册中心url,然后遍历调用doExportUrlsFor1Protocol方法。对于在标签中指定了registry属性的Bean,会在加载BeanDefinition的时候就加载了注册中心。

ServiceConfig.java 类的 export 方法

package com.alibaba.dubbo.config;


public
 
class
 
ServiceConfig
<T> 
extends
 
AbstractServiceConfig
 {


public
 
synchronized
 
void
 export() {

    
if
 (provider != 
null
) {

        
if
 (export == 
null
) {

            export = provider.getExport();

        }

        
if
 (delay == 
null
) {

            delay = provider.getDelay();

        }

    }

    
if
 (export != 
null
 && !export) {

        
return
;

    }


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

        delayExportExecutor.schedule(
new
 
Runnable
() {

            
public
 
void
 run() {

                doExport();

            }

        }, delay, 
TimeUnit
.MILLISECONDS);

    } 
else
 {

        doExport();

    }

}

可以看出发布发布是支持延迟暴露发布服务的,这样可以用于当我们发布的服务非常多,影响到应用启动的问题,前提是应用允许服务发布的延迟特性。

接下来就进入到 ServiceConfig.java 类的 doExport() 方法。

检查DUBBO配置的合法性

ServiceConfig.java 类的 doExport方法。检查DUBBO配置的合法性,并调用doExportUrls 方法。

package com.alibaba.dubbo.config;


public
 
class
 
ServiceConfig
<T> 
extends
 
AbstractServiceConfig
 {


protected
 
synchronized
 
void
 doExport() {

       
// 省略。。。

        checkApplication();

        checkRegistry();

        checkProtocol();

        appendProperties(
this
);

        checkStubAndMock(interfaceClass);

        
if
 (path == 
null
 || path.length() == 
0
) {

            path = interfaceName;

        }

        doExportUrls();

    }

}

我们可以看出该方法的实现的逻辑包含了根据配置的优先级将 ProviderConfig,ModuleConfig,MonitorConfig,ApplicaitonConfig等一些配置信息进行组装和合并。还有一些逻辑是检查配置信息的合法性。最后又调用了doExportUrls方法。

服务多协议暴露过程

ServiceConfig.java 类的 doExportUrls() 方法

package com.alibaba.dubbo.config;


public
 
class
 
ServiceConfig
<T> 
extends
 
AbstractServiceConfig
 {


    
@SuppressWarnings
({
"unchecked"
, 
"rawtypes"
})

    
private
 
void
 doExportUrls() {

        
List
<URL> registryURLs = loadRegistries(
true
);

        
for
 (
ProtocolConfig
 protocolConfig : protocols) {

            doExportUrlsFor1Protocol(protocolConfig, registryURLs);

        }

    }

}

该方法第一步是加载注册中心列表

loadRegistries(true);加载注册中心列表响应示例

registry:
//127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo-provider&dubbo=2.5.6&file=/data/dubbo/cache/dubbo-provider&pid=21448&registry=zookeeper&timestamp=1524134852031

 

第二部是将服务发布到多种协议的url上,并且携带注册中心列表的参数,从这里我们可以看出dubbo是支持同时将一个服务发布成为多种协议的,这个需求也是很正常的,客户端也需要支持多协议,根据不同的场景选择合适的协议。

ServiceConfig.java 类的 doExportUrlsFor1Protocol(ProtocolConfigprotocolConfig,List<URL>registryURLs) 方法。

package com.alibaba.dubbo.config;


public
 
class
 
ServiceConfig
<T> 
extends
 
AbstractServiceConfig
 {


    
private
 
void
 doExportUrlsFor1Protocol(
ProtocolConfig
 protocolConfig, 
List
<URL> registryURLs) {

        // 省略很多

            
if
 (!
Constants
.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {


                
//配置不是remote的情况下做本地暴露 (配置为remote,则表示只暴露远程服务)

                
if
 (!
Constants
.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {

                    exportLocal(url);

                }

                
//如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露本地服务)

                
if
 (!
Constants
.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {

                    
if
 (logger.isInfoEnabled()) {

                        logger.info(
"Export dubbo service "
 + interfaceClass.getName() + 
" to url "
 + url);

                    }

                    
if
 (registryURLs != 
null
 && registryURLs.size() > 
0

                            && url.getParameter(
"register"
, 
true
)) {

                        
for
 (URL registryURL : registryURLs) {

                            url = url.addParameterIfAbsent(
"dynamic"
, registryURL.getParameter(
"dynamic"
));

                            URL monitorUrl = loadMonitor(registryURL);

                            
if
 (monitorUrl != 
null
) {

                                url = url.addParameterAndEncoded(
Constants
.MONITOR_KEY, monitorUrl.toFullString());

                            }

                            
if
 (logger.isInfoEnabled()) {

                                logger.info(
"Register dubbo service "
 + interfaceClass.getName() + 
" url "
 + url + 
" to registry "
 + registryURL);

                            }

                            
Invoker
<?> invoker = proxyFactory.getInvoker(ref, (
Class
) interfaceClass, registryURL.addParameterAndEncoded(
Constants
.EXPORT_KEY, url.toFullString()));


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

                            exporters.add(exporter);

                        }

                    } 
else
 {

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


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

                        exporters.add(exporter);

                    }

                }

            }

            
this
.urls.add(url);

        }

}

拼装dubbo服务URL

该方法的逻辑是先根据服务配置、协议配置、发布服务的服务器信息、方法列表、dubbo版本等等信息组装成一个发布的URL对象。

主要根据之前map里的数据组装成URL。

例如

dubbo://10.4.81.95:20880/io.ymq.dubbo.api.DemoService?

anyhost=
true
&

application=dubbo-provider&

default
.connections=
5
&

default
.delay=-
1
&

default
.retries=
0
&

default
.timeout=
10000
&

default
.version=
1.0
&

delay=-
1
&

dubbo=
2.5
.
6
&

generic
=
false
&

interface
=io.ymq.dubbo.api.
DemoService
&

methods=sayHello&

pid=
21448
&

side=provider&

threadpool=
fixed
&

threads=
500
&

timestamp=
1524135271940

本地暴露和远程暴露

  1. 如果服务配置的scope是发布范围,配置为none不暴露服务,则会停止发布操作;

  2. 如果配置不是remote的情况下先做本地暴露,则调用本地暴露exportLocal方法;

  3. 如果配置不是local则暴露为远程服务,则注册服务registryProcotol;

//配置为none不暴露

if
 (!
Constants
.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {


    
//配置不是remote的情况下做本地暴露 (配置为remote,则表示只暴露远程服务)

    
if
 (!
Constants
.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {

        exportLocal(url);

    }

    
//如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露本地服务)

    
if
 (!
Constants
.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {

        省略更多

    }

}

最新2020整理收集的一些面试题(都整理成文档),有很多干货,包含mysql,netty,spring,线程,spring cloud、jvm、源码、算法等详细讲解,也有详细的学习规划图,面试题整理等,需要获取这些内容的朋友微信扫描下方二维码免费获取:暗号:【CSDN】

面试必备:Dubbo中暴露服务的过程解析

面试必备:Dubbo中暴露服务的过程解析

看完三件事❤️

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:

  1. 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。

  2. 关注公众号 『 java烂猪皮 』,不定期分享原创知识。

  3. 同时可以期待后续文章ing????

面试必备:Dubbo中暴露服务的过程解析