一、dubbo的分層架構(gòu)
1、dubbo的分層架構(gòu)
在具體將dubbo服務(wù)暴露和消費(fèi)之前,我們還是限流看下dubbo的分成架構(gòu)


Service和Config兩層可以認(rèn)為是API層,主要提供給API使用者,使用者無(wú)須關(guān)心底層 的實(shí)現(xiàn),只需要配置和完成業(yè)務(wù)代碼即可;后面所有的層級(jí)合在一起,可以認(rèn)為是SPI層,主要提供給擴(kuò)展者使用,即用戶可以基于Dubb??蚣茏龆ㄖ菩缘亩伍_發(fā),擴(kuò)展其功能。Dubbo的擴(kuò)展能力非常強(qiáng),這也是Dubbo一直廣受歡迎的原因之一.Dubbo框架中的分層代表了不同的邏輯實(shí)現(xiàn),它們是一個(gè)個(gè)組件,這些組件構(gòu)成了整個(gè)Dubbo體系,在使用方角度更多接觸到的可能是配置,更多底層構(gòu)件被抽象和隱藏了。我們先整體上看一下dubbo的整體分層,這里不太理解的小伙伴不用著急,我們先有個(gè)整體概念。隨著我們后面看完服務(wù)暴露和消費(fèi)是細(xì)節(jié)流程,回過頭再來看這個(gè)分層模型,應(yīng)該就會(huì)容易理解很多。
二、dubbo服務(wù)暴露
2.1. 服務(wù)暴露整體流程

如圖所示:服務(wù)暴露的整體上分成兩步,第一步將需要暴露的服務(wù)實(shí)例通過代理轉(zhuǎn)換成Invoker,第二步會(huì)將invoker通過具體的協(xié)議(protocol)比如dubboProtocol協(xié)議遠(yuǎn)程暴露后轉(zhuǎn)換成Exporter。這里的Invoker對(duì)象可以簡(jiǎn)單理解成一個(gè)真實(shí)的服務(wù)對(duì)象實(shí)例,最重要的是我們可以對(duì)它發(fā)起invoke調(diào)用,通過invoke調(diào)用就可以暴露和消費(fèi)服務(wù)。Invoker可以是一個(gè)本地的實(shí)現(xiàn)(服務(wù)內(nèi)部通過jvm調(diào)用),也可以說是一個(gè)遠(yuǎn)程實(shí)現(xiàn)(跨jvm調(diào)用),還可能是一個(gè)集群實(shí)現(xiàn)(服務(wù)有多個(gè)提供者)。下面我們就來看一下服務(wù)暴露的流程細(xì)節(jié)。
2.2 dubbo與spring集成
在說dubbo服務(wù)入口之前,我們先來看一下dubbo和spring集成的部分。
我們知道,不論是xml還是注解的形式,duboo都有提供很多變遷,比如我們?cè)诜?wù)暴露的過程中,會(huì)將我們提供出去的bean標(biāo)記成Service,以服務(wù)端xml配置為例,dubbo-provicer.xml:
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- provider's application name, used for tracing dependency relationship -->
<dubbo:application name="demo-provider"/>
<dubbo:registry address="zookeeper://114.132.223.55:3888?backup=114.132.223.55,114.132.223.55" />
<!-- use dubbo protocol to export service on port 20880 -->
<dubbo:protocol name="dubbo"/>
<!-- service implementation, as same as regular local bean -->
<bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl"/>
<!-- declare the service interface to be exported -->
<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService"/>
</beans>
從xml配置中我們可以看到,我們將demoService定義成了 dubbo:service 標(biāo)簽類型。實(shí)際上,dubbo中有自己的一套標(biāo)簽解析器,在spring啟動(dòng)加載的時(shí)候,將所有用到的dubbo標(biāo)簽解析成相關(guān)的bean,并進(jìn)行后續(xù)處理,比如服務(wù)暴露或消費(fèi)等。而在服務(wù)暴露的過程中,就是將service 標(biāo)簽通過解析器解析成了ServiceBean進(jìn)行后續(xù)處理,這部分代碼入口在DubboNamespaceHandler中,代碼如下:
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
@Override
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("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.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 AnnotationBeanDefinitionParser());
}
}
如圖所示:首先DubboNamespaceHandler 繼承了NamespaceHandlerSupport,在init方法(init方法執(zhí)行時(shí)機(jī):spring在獲取bean工廠的時(shí)候,此時(shí)會(huì)加載bean的解析器)中通過registerBeanDefinitionParser 方法給spring設(shè)置我們自定義的dubbo標(biāo)簽解析器,后面spring在加載bean的定義信息的時(shí)候,就可以按照我們的解析器去解析,例如截圖中的service 和 refrence,DubboBeanDefinitionParser就會(huì)將配置文件中的bean設(shè)置成不同類型,配置文件中的 service標(biāo)簽對(duì)應(yīng)的bean就會(huì)設(shè)置成 ServiceBean的類型,而對(duì)于這些bean,后續(xù)被創(chuàng)建以后,就會(huì)當(dāng)成服務(wù)被暴露出去。下面我們來看下ServiceBean中是怎么走到服務(wù)暴露的邏輯的。
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware,
ApplicationEventPublisherAware {
......
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
SpringExtensionFactory.addApplicationContext(applicationContext);
supportedApplicationListener = addApplicationListener(applicationContext, this);
}
@Override
public void setBeanName(String name) {
this.beanName = name;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
@Override
@SuppressWarnings({"unchecked", "deprecation"})
public void afterPropertiesSet() throws Exception{
......
//設(shè)置bubbo的各種配置中心、監(jiān)控中心等等
......
}
@Override
public void export() {
super.export();
// Publish ServiceBeanExportedEvent
publishExportEvent();
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
- 繼承了ServiceConfig 這個(gè)類,主要為了后續(xù)暴露服務(wù)調(diào)用父類的export方法。
- 實(shí)現(xiàn)了 ApplicationContextAware,是獲取到 applicaltionContext上下文,
- 實(shí)現(xiàn)了 BeanNameAware,獲取到bean 的名稱
- 實(shí)現(xiàn)了 ApplicationEventPublisherAware是設(shè)置事件發(fā)布器
- 實(shí)現(xiàn)了ApplicationListener 接口,是在bean實(shí)例化完成之后,finishRefresh方法調(diào)用,在全部bean都實(shí)例完成之后,開始事件通知相應(yīng)的service 暴露服務(wù)。
看到這里,我們基本上就看到了服務(wù)暴露的入口了,在spring完成bean實(shí)例化之后,事件通知響應(yīng)的service開始暴露服務(wù),也就是在 onApplicationEvent中服務(wù)暴露入口 export,具體暴露交給了父類serviceConfig。
2.3 服務(wù)暴露具體細(xì)節(jié)
為了弄清主流程,一些旁枝末節(jié)我們?cè)诖司筒蛔龇治?,代碼分析從 org.apache.dubbo.config.ServiceConfig#doExportUrls 開始。
private void doExportUrls() {
//加載注冊(cè)中心
List<URL> 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);
}
}
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs){
......
appendRuntimeParameters(map);
appendParameters(map, metrics);
appendParameters(map, application);
appendParameters(map, module);
// remove 'default.' prefix for configs from ProviderConfig
// appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, provider);
appendParameters(map, protocolConfig);
appendParameters(map, this);
.......
/**
* ①以上有所刪減,目的是構(gòu)造Url,其中url包含了暴露的服務(wù)路徑、方法、協(xié)議、主機(jī)、端口、參數(shù)等等
*/
//根據(jù)scpoe配置來暴露服務(wù),如果scpoe不配置,默認(rèn)本地和遠(yuǎn)程都會(huì)暴露
String scope = url.getParameter(SCOPE_KEY);
// don't export when none is configured
if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
// export to local if the config is not remote (export to remote only when config is remote)
if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
/**
* ②本地服務(wù)暴露
*/
exportLocal(url);
}
// export to remote if the config is not local (export to local only when config is local)
if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
if (!isOnlyInJvm() && logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
if (CollectionUtils.isNotEmpty(registryURLs)) {
for (URL registryURL : registryURLs) {
//if protocol is only injvm ,not register
if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
continue;
}
url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
/**
* ③如果配置了監(jiān)控地址,則服務(wù)調(diào)用信息會(huì)上報(bào)
*/
url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
// For providers, this is used to enable custom proxy to generate invoker
String proxy = url.getParameter(PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(PROXY_KEY, proxy);
}
/**
* ④代理模式獲取invoker,默認(rèn)javassist
*/
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
/**
* ⑤暴露invoker
*/
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
}
/**
* ⑥處理沒有注冊(cè)中心場(chǎng)景,直接暴露服務(wù)
*/
else {
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
/**
* @since 2.7.0
* ServiceData Store
*/
MetadataReportService metadataReportService = null;
if ((metadataReportService = getMetadataReportService()) != null) {
metadataReportService.publishProvider(url);
}
}
}
}
通讀以上代碼,在2.1 服務(wù)暴露主流程中我們提到,整個(gè)服務(wù)暴露的主干,其實(shí)就是將 senviceConfig通過代理轉(zhuǎn)換成invoker,然后再通過具體協(xié)議將invoker暴露出去最后返回exporter的過程?;诖耍覀儊砜醋⑨屩械?①- ⑥
①主要通過反射獲取配置對(duì)象并放到map中,用于構(gòu)造url參數(shù),這塊的代碼較長(zhǎng),為了突出主流程,有所刪減。url參數(shù)中包括了很多參數(shù)信息,像需要暴露的服務(wù)路徑、方法、協(xié)議、主機(jī)、端口等
②主要是本地服務(wù)的暴露,通常dubbo會(huì)把遠(yuǎn)程服務(wù)用injvm協(xié)議再暴露一份,這樣消費(fèi)方直接消費(fèi)同一個(gè)jvm內(nèi)部的服務(wù),避免了跨網(wǎng)絡(luò)遠(yuǎn)程工通信。所以本地服務(wù)相比遠(yuǎn)程暴露來說,少了打開遠(yuǎn)程注冊(cè)中心端口通信的過程,僅僅是把invoker保存在內(nèi)存中并返回exporter。可以點(diǎn)進(jìn) exportLocal 方法具體看一下
private void exportLocal(URL url) {
URL local = URLBuilder.from(url)
.setProtocol(LOCAL_PROTOCOL)
.setHost(LOCALHOST_VALUE)
.setPort(0)
.build();
Exporter<?> exporter = protocol.export(
//通過javassist代理方式獲取實(shí)現(xiàn)類的代理
PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
}
③中主要追加監(jiān)控上報(bào)地址,框架會(huì)在攔截器中執(zhí)行數(shù)據(jù)上報(bào),這部分是可選的。
④通過動(dòng)態(tài)代理的方式創(chuàng)建代理對(duì)象,這部分是重點(diǎn),就是將serviceConfig通過代理轉(zhuǎn)換成invoker。