一膨疏、dubbo的分層架構(gòu)
1照藻、dubbo的分層架構(gòu)
在具體將dubbo服務(wù)暴露和消費之前,我們還是限流看下dubbo的分成架構(gòu)
Service和Config兩層可以認(rèn)為是API層魔策,主要提供給API使用者氓皱,使用者無須關(guān)心底層 的實現(xiàn),只需要配置和完成業(yè)務(wù)代碼即可续室;后面所有的層級合在一起栋烤,可以認(rèn)為是SPI層,主要提供給擴(kuò)展者使用挺狰,即用戶可以基于Dubb明郭。框架做定制性的二次開發(fā)丰泊,擴(kuò)展其功能薯定。Dubbo的擴(kuò)展能力非常強(qiáng),這也是Dubbo一直廣受歡迎的原因之一.Dubbo框架中的分層代表了不同的邏輯實現(xiàn)瞳购,它們是一個個組件话侄,這些組件構(gòu)成了整個Dubbo體系,在使用方角度更多接觸到的可能是配置苛败,更多底層構(gòu)件被抽象和隱藏了满葛。我們先整體上看一下dubbo的整體分層,這里不太理解的小伙伴不用著急罢屈,我們先有個整體概念嘀韧。隨著我們后面看完服務(wù)暴露和消費是細(xì)節(jié)流程,回過頭再來看這個分層模型缠捌,應(yīng)該就會容易理解很多锄贷。
二、dubbo服務(wù)暴露
2.1. 服務(wù)暴露整體流程
如圖所示:服務(wù)暴露的整體上分成兩步曼月,第一步將需要暴露的服務(wù)實例通過代理轉(zhuǎn)換成Invoker谊却,第二步會將invoker通過具體的協(xié)議(protocol)比如dubboProtocol協(xié)議遠(yuǎn)程暴露后轉(zhuǎn)換成Exporter。這里的Invoker對象可以簡單理解成一個真實的服務(wù)對象實例哑芹,最重要的是我們可以對它發(fā)起invoke調(diào)用炎辨,通過invoke調(diào)用就可以暴露和消費服務(wù)。Invoker可以是一個本地的實現(xiàn)(服務(wù)內(nèi)部通過jvm調(diào)用)聪姿,也可以說是一個遠(yuǎn)程實現(xiàn)(跨jvm調(diào)用)碴萧,還可能是一個集群實現(xiàn)(服務(wù)有多個提供者)。下面我們就來看一下服務(wù)暴露的流程細(xì)節(jié)末购。
2.2 dubbo與spring集成
在說dubbo服務(wù)入口之前破喻,我們先來看一下dubbo和spring集成的部分。
我們知道盟榴,不論是xml還是注解的形式曹质,duboo都有提供很多變遷,比如我們在服務(wù)暴露的過程中,會將我們提供出去的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)簽類型玩般。實際上银觅,dubbo中有自己的一套標(biāo)簽解析器,在spring啟動加載的時候坏为,將所有用到的dubbo標(biāo)簽解析成相關(guān)的bean,并進(jìn)行后續(xù)處理镊绪,比如服務(wù)暴露或消費等匀伏。而在服務(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í)行時機(jī):spring在獲取bean工廠的時候榄鉴,此時會加載bean的解析器)中通過registerBeanDefinitionParser 方法給spring設(shè)置我們自定義的dubbo標(biāo)簽解析器履磨,后面spring在加載bean的定義信息的時候,就可以按照我們的解析器去解析庆尘,例如截圖中的service 和 refrence剃诅,DubboBeanDefinitionParser就會將配置文件中的bean設(shè)置成不同類型,配置文件中的 service標(biāo)簽對應(yīng)的bean就會設(shè)置成 ServiceBean的類型驶忌,而對于這些bean矛辕,后續(xù)被創(chuàng)建以后,就會當(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 這個類几苍,主要為了后續(xù)暴露服務(wù)調(diào)用父類的export方法翻屈。
- 實現(xiàn)了 ApplicationContextAware,是獲取到 applicaltionContext上下文妻坝,
- 實現(xiàn)了 BeanNameAware伸眶,獲取到bean 的名稱
- 實現(xiàn)了 ApplicationEventPublisherAware是設(shè)置事件發(fā)布器
- 實現(xiàn)了ApplicationListener 接口,是在bean實例化完成之后惠勒,finishRefresh方法調(diào)用赚抡,在全部bean都實例完成之后,開始事件通知相應(yīng)的service 暴露服務(wù)纠屋。
看到這里涂臣,我們基本上就看到了服務(wù)暴露的入口了,在spring完成bean實例化之后,事件通知響應(yīng)的service開始暴露服務(wù)赁遗,也就是在 onApplicationEvent中服務(wù)暴露入口 export署辉,具體暴露交給了父類serviceConfig。
2.3 服務(wù)暴露具體細(xì)節(jié)
為了弄清主流程岩四,一些旁枝末節(jié)我們在此就不做分析哭尝,代碼分析從 org.apache.dubbo.config.ServiceConfig#doExportUrls
開始。
private void doExportUrls() {
//加載注冊中心
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)程都會暴露
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)用信息會上報
*/
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);
}
}
/**
* ⑥處理沒有注冊中心場景规脸,直接暴露服務(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ù)暴露主流程中我們提到,整個服務(wù)暴露的主干莫鸭,其實就是將 senviceConfig通過代理轉(zhuǎn)換成invoker闹丐,然后再通過具體協(xié)議將invoker暴露出去最后返回exporter的過程∏辏基于此妇智,我們來看注釋中的 ①- ⑥
①主要通過反射獲取配置對象并放到map中,用于構(gòu)造url參數(shù)氏身,這塊的代碼較長巍棱,為了突出主流程,有所刪減蛋欣。url參數(shù)中包括了很多參數(shù)信息航徙,像需要暴露的服務(wù)路徑、方法陷虎、協(xié)議到踏、主機(jī)、端口等
②主要是本地服務(wù)的暴露尚猿,通常dubbo會把遠(yuǎn)程服務(wù)用injvm協(xié)議再暴露一份窝稿,這樣消費方直接消費同一個jvm內(nèi)部的服務(wù),避免了跨網(wǎng)絡(luò)遠(yuǎn)程工通信凿掂。所以本地服務(wù)相比遠(yuǎn)程暴露來說伴榔,少了打開遠(yuǎn)程注冊中心端口通信的過程纹蝴,僅僅是把invoker保存在內(nèi)存中并返回exporter∽偕伲可以點進(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代理方式獲取實現(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)控上報地址塘安,框架會在攔截器中執(zhí)行數(shù)據(jù)上報,這部分是可選的援奢。
④通過動態(tài)代理的方式創(chuàng)建代理對象兼犯,這部分是重點,就是將serviceConfig通過代理轉(zhuǎn)換成invoker集漾。