SpringCloud配置刷新機(jī)制的簡(jiǎn)單分析[nacos為例子]
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
? 作者?|? OhOutOfMemoryError
來(lái)源 |? urlify.cn/nInERf
76套java從入門到精通實(shí)戰(zhàn)課程分享
SpringCloud Nacos
本文主要分為SpringCloud Nacos的設(shè)計(jì)思路
簡(jiǎn)單分析一下觸發(fā)刷新事件后發(fā)生的過(guò)程以及一些踩坑經(jīng)驗(yàn)
org.springframework.cloud.bootstrap.config.PropertySourceLocator
這是一個(gè)SpringCloud提供的啟動(dòng)器加載配置類,實(shí)現(xiàn)locate,注入到上下文中即可發(fā)現(xiàn)配置
/**
?*?@param?environment?The?current?Environment.
?*?@return?A?PropertySource,?or?null?if?there?is?none.
?*?@throws?IllegalStateException?if?there?is?a?fail-fast?condition.
?*/
PropertySource>?locate(Environment?environment);
com.alibaba.cloud.nacos.client.NacosPropertySourceLocator
該類為nacos實(shí)現(xiàn)的配置發(fā)現(xiàn)類
org.springframework.core.env.PropertySource
改類為springcloud抽象出來(lái)表達(dá)屬性源的類
com.alibaba.cloud.nacos.client.NacosPropertySource / nacos實(shí)現(xiàn)了這個(gè)類,并賦予了其他屬性
/**
?*?Nacos?Group.
?*/
private?final?String?group;
/**
?*?Nacos?dataID.
?*/
private?final?String?dataId;
/**
?*?timestamp?the?property?get.
?*/
private?final?Date?timestamp;
/**
?*?Whether?to?support?dynamic?refresh?for?this?Property?Source.
?*/
private?final?boolean?isRefreshable;
大概講解com.alibaba.cloud.nacos.client.NacosPropertySourceLocator#locate
源碼解析
@Override
public?PropertySource>?locate(Environment?env)?{
?nacosConfigProperties.setEnvironment(env);
?//?獲取nacos配置的服務(wù)類,http協(xié)議,訪問(wèn)nacos的api接口獲得配置
?ConfigService?configService?=?nacosConfigManager.getConfigService();
?if?(null?==?configService)?{
??log.warn("no?instance?of?config?service?found,?can't?load?config?from?nacos");
??return?null;
?}
?long?timeout?=?nacosConfigProperties.getTimeout();
?//?構(gòu)建一個(gè)builder
?nacosPropertySourceBuilder?=?new?NacosPropertySourceBuilder(configService,
???timeout);
?String?name?=?nacosConfigProperties.getName();
?String?dataIdPrefix?=?nacosConfigProperties.getPrefix();
?if?(StringUtils.isEmpty(dataIdPrefix))?{
??dataIdPrefix?=?name;
?}
?if?(StringUtils.isEmpty(dataIdPrefix))?{
??dataIdPrefix?=?env.getProperty("spring.application.name");
?}
????//?構(gòu)建一個(gè)復(fù)合數(shù)據(jù)源
?CompositePropertySource?composite?=?new?CompositePropertySource(
???NACOS_PROPERTY_SOURCE_NAME);
????//?加載共享的配置
?loadSharedConfiguration(composite);
?//?加載擴(kuò)展配置
?loadExtConfiguration(composite);
?//?加載應(yīng)用配置,應(yīng)用配置的優(yōu)先級(jí)是最高,所以這里放在最后面來(lái)做,是因?yàn)樘砑优渲玫牡胤蕉际莂ddFirst,所以最先的反而優(yōu)先級(jí)最后
?loadApplicationConfiguration(composite,?dataIdPrefix,?nacosConfigProperties,?env);
?return?composite;
}
每次nacos檢查到配置更新的時(shí)候就會(huì)觸發(fā)上下文配置刷新,就會(huì)調(diào)取locate這個(gè)方法
org.springframework.cloud.endpoint.event.RefreshEvent
該事件為spring cloud內(nèi)置的事件,用于刷新配置
com.alibaba.cloud.nacos.refresh.NacosRefreshHistory
該類用于nacos刷新歷史的存放,用來(lái)保存每次拉取的配置的md5值,用于比較配置是否需要刷新
com.alibaba.cloud.nacos.refresh.NacosContextRefresher
該類是Nacos用來(lái)管理一些內(nèi)部監(jiān)聽(tīng)器的,主要是配置刷新的時(shí)候可以出發(fā)回調(diào),并且發(fā)出spring cloud上下文的配置刷新事件
com.alibaba.cloud.nacos.NacosPropertySourceRepository
該類是nacos用來(lái)保存拉取到的數(shù)據(jù)的
流程:
刷新器檢查到配置更新,保存到NacosPropertySourceRepository
發(fā)起刷新事件
locate執(zhí)行,直接讀取NacosPropertySourceRepository
com.alibaba.nacos.client.config.NacosConfigService
該類是nacos的主要刷新配置服務(wù)類
com.alibaba.nacos.client.config.impl.ClientWorker
該類是服務(wù)類里主要的客戶端,協(xié)議是HTTP
clientWorker啟動(dòng)的時(shí)候會(huì)初始化2個(gè)線程池,1個(gè)用于定時(shí)檢查配置,1個(gè)用于輔助檢查
executor?=?Executors.newScheduledThreadPool(1,?new?ThreadFactory()?{
????????????@Override
????????????public?Thread?newThread(Runnable?r)?{
????????????????Thread?t?=?new?Thread(r);
????????????????t.setName("com.alibaba.nacos.client.Worker."?+?agent.getName());
????????????????t.setDaemon(true);
????????????????return?t;
????????????}
????????});
executorService?=?Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(),?new?ThreadFactory()?{
????@Override
????public?Thread?newThread(Runnable?r)?{
????????Thread?t?=?new?Thread(r);
????????t.setName("com.alibaba.nacos.client.Worker.longPolling."?+?agent.getName());
????????t.setDaemon(true);
????????return?t;
????}
});
executor.scheduleWithFixedDelay(new?Runnable()?{
????@Override
????public?void?run()?{
????????try?{
????????????checkConfigInfo();
????????}?catch?(Throwable?e)?{
????????????LOGGER.error("["?+?agent.getName()?+?"]?[sub-check]?rotate?check?error",?e);
????????}
????}
},?1L,?10L,?TimeUnit.MILLISECONDS);
com.alibaba.nacos.client.config.impl.ClientWorker.LongPollingRunnable
該類用于長(zhǎng)輪詢?nèi)蝿?wù)
com.alibaba.nacos.client.config.impl.CacheData#checkListenerMd5比對(duì)MD5之后開(kāi)始刷新配置
com.alibaba.cloud.nacos.parser
該包提供了很多文件類型的轉(zhuǎn)換器
加載數(shù)據(jù)的時(shí)候會(huì)根據(jù)文件擴(kuò)展名去查找一個(gè)轉(zhuǎn)換器實(shí)例
//?com.alibaba.cloud.nacos.client.NacosPropertySourceBuilder#loadNacosData
private?Map?loadNacosData(String?dataId,?String?group,
???String?fileExtension)?{
?String?data?=?null;
?try?{
??data?=?configService.getConfig(dataId,?group,?timeout);
??if?(StringUtils.isEmpty(data))?{
???log.warn(
?????"Ignore?the?empty?nacos?configuration?and?get?it?based?on?dataId[{}]?&?group[{}]",
?????dataId,?group);
???return?EMPTY_MAP;
??}
??if?(log.isDebugEnabled())?{
???log.debug(String.format(
?????"Loading?nacos?data,?dataId:?'%s',?group:?'%s',?data:?%s",?dataId,
?????group,?data));
??}
??Map?dataMap?=?NacosDataParserHandler.getInstance()
????.parseNacosData(data,?fileExtension);
??return?dataMap?==?null???EMPTY_MAP?:?dataMap;
?}
?catch?(NacosException?e)?{
??log.error("get?data?from?Nacos?error,dataId:{},?",?dataId,?e);
?}
?catch?(Exception?e)?{
??log.error("parse?data?from?Nacos?error,dataId:{},data:{},",?dataId,?data,?e);
?}
?return?EMPTY_MAP;
}
數(shù)據(jù)會(huì)變成key value的形式,然后轉(zhuǎn)換成PropertySource
如何配置一個(gè)啟動(dòng)配置類
由于配置上下文是屬于SpringCloud管理的,所以本次的注入跟以往SpringBoot不一樣
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration
如何在SpringCloud和SpringBoot共享一個(gè)bean呢(舉個(gè)例子)
@Bean
public?NacosConfigProperties?nacosConfigProperties(ApplicationContext?context)?{
?if?(context.getParent()?!=?null
???&&?BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
?????context.getParent(),?NacosConfigProperties.class).length?>?0)?{
??return?BeanFactoryUtils.beanOfTypeIncludingAncestors(context.getParent(),
????NacosConfigProperties.class);
?}
?return?new?NacosConfigProperties();
}
關(guān)于刷新機(jī)制的流程
org.springframework.cloud.endpoint.event.RefreshEventListener
//?外層方法
public?synchronized?Set?refresh()?{
?Set?keys?=?refreshEnvironment();
?this.scope.refreshAll();
?return?keys;
}
//?
public?synchronized?Set?refreshEnvironment()?{
?Map?before?=?extract(
???this.context.getEnvironment().getPropertySources());
?addConfigFilesToEnvironment();
?Set?keys?=?changes(before,
???extract(this.context.getEnvironment().getPropertySources())).keySet();
?this.context.publishEvent(new?EnvironmentChangeEvent(this.context,?keys));
?return?keys;
}
該類是對(duì)RefreshEvent監(jiān)聽(tīng)的處理
直接定位到org.springframework.cloud.context.refresh.ContextRefresher#refreshEnvironment,這個(gè)方法是主要的刷新配置的方法,具體做的事:
歸并得到刷新之前的配置key value
org.springframework.cloud.context.refresh.ContextRefresher#addConfigFilesToEnvironment 模擬一個(gè)新的SpringApplication,觸發(fā)大部分的SpringBoot啟動(dòng)流程,因此也會(huì)觸發(fā)讀取配置,于是就會(huì)觸發(fā)上文所講的Locator,然后得到一個(gè)新的Spring應(yīng)用,從中獲取新的聚合配置源,與舊的Spring應(yīng)用配置源進(jìn)行比較,并且把本次變更的配置放置到舊的去,然后把新的Spring應(yīng)用關(guān)閉
比較新舊配置,把配置拿出來(lái),觸發(fā)一個(gè)事件org.springframework.cloud.context.environment.EnvironmentChangeEvent
跳出該方法棧后,執(zhí)行org.springframework.cloud.context.scope.refresh.RefreshScope#refreshAll
簡(jiǎn)單分析 EnvironmentChangeEvent
org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#rebind()
代碼如下:
@ManagedOperation
public?boolean?rebind(String?name)?{
?if?(!this.beans.getBeanNames().contains(name))?{
??return?false;
?}
?if?(this.applicationContext?!=?null)?{
??try?{
???Object?bean?=?this.applicationContext.getBean(name);
???//?獲取source對(duì)象
???if?(AopUtils.isAopProxy(bean))?{
????bean?=?ProxyUtils.getTargetObject(bean);
???}
???if?(bean?!=?null)?{
????//?重新觸發(fā)銷毀和初始化的周期方法
????this.applicationContext.getAutowireCapableBeanFactory()
??????.destroyBean(bean);
???????//?因?yàn)橛|發(fā)初始化生命周期,就可以觸發(fā)
???????//?org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization
????this.applicationContext.getAutowireCapableBeanFactory()
??????.initializeBean(bean,?name);
????return?true;
???}
??}
??catch?(RuntimeException?e)?{
???this.errors.put(name,?e);
???throw?e;
??}
??catch?(Exception?e)?{
???this.errors.put(name,?e);
???throw?new?IllegalStateException("Cannot?rebind?to?"?+?name,?e);
??}
?}
?return?false;
}
該方法時(shí)接受到事件后,對(duì)一些bean進(jìn)行屬性重綁定,具體哪些Bean呢?
org.springframework.cloud.context.properties.ConfigurationPropertiesBeans#postProcessBeforeInitialization 該方法會(huì)在Spring refresh上下文時(shí)候執(zhí)行的bean生命后期里的其中一個(gè)后置處理器,它會(huì)檢查注解 @ConfigurationProperties,這些bean就是上面第一步講的重綁定的bean
@Override
public?Object?postProcessBeforeInitialization(Object?bean,?String?beanName)
??throws?BeansException?{
?if?(isRefreshScoped(beanName))?{
??return?bean;
?}
?ConfigurationProperties?annotation?=?AnnotationUtils
???.findAnnotation(bean.getClass(),?ConfigurationProperties.class);
?if?(annotation?!=?null)?{
??this.beans.put(beanName,?bean);
?}
?else?if?(this.metaData?!=?null)?{
??annotation?=?this.metaData.findFactoryAnnotation(beanName,
????ConfigurationProperties.class);
??if?(annotation?!=?null)?{
???this.beans.put(beanName,?bean);
??}
?}
?return?bean;
}
簡(jiǎn)單分析org.springframework.cloud.context.scope.refresh.RefreshScope#refreshAll
@ManagedOperation(description?=?"Dispose?of?the?current?instance?of?all?beans?"
???+?"in?this?scope?and?force?a?refresh?on?next?method?execution.")
public?void?refreshAll()?{
?super.destroy();
?this.context.publishEvent(new?RefreshScopeRefreshedEvent());
}
org.springframework.cloud.context.scope.GenericScope#destroy()
對(duì)BeanLifecycleWrapper實(shí)例集合進(jìn)行銷毀
BeanLifecycleWrapper是什么?
private?static?class?BeanLifecycleWrapper?{
????//?bean的名字
?private?final?String?name;
????//?獲取bean
?private?final?ObjectFactory>?objectFactory;
????//?真正的實(shí)例
?private?Object?bean;
????//?銷毀函數(shù)
?private?Runnable?callback;
}?
BeanLifecycleWrapper是怎么構(gòu)造的?
@Override
public?Object?get(String?name,?ObjectFactory>?objectFactory)?{
?BeanLifecycleWrapper?value?=?this.cache.put(name,
???new?BeanLifecycleWrapper(name,?objectFactory));
?this.locks.putIfAbsent(name,?new?ReentrantReadWriteLock());
?try?{
??return?value.getBean();
?}
?catch?(RuntimeException?e)?{
??this.errors.put(name,?e);
??throw?e;
?}
}
以上代碼可以追溯到Spring在創(chuàng)建bean的某一個(gè)分支代碼,org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 347行代碼
String?scopeName?=?mbd.getScope();
final?Scope?scope?=?this.scopes.get(scopeName);
if?(scope?==?null)?{
?throw?new?IllegalStateException("No?Scope?registered?for?scope?name?'"?+?scopeName?+?"'");
}
try?{
?Object?scopedInstance?=?scope.get(beanName,?()?->?{
??beforePrototypeCreation(beanName);
??try?{
???return?createBean(beanName,?mbd,?args);
??}
??finally?{
???afterPrototypeCreation(beanName);
??}
?});
?bean?=?getObjectForBeanInstance(scopedInstance,?name,?beanName,?mbd);
}
銷毀完之后呢?其實(shí)就是把BeanLifecycleWrapper綁定的bean變成了null,那配置怎么刷新呢?@RefreshScope標(biāo)記的對(duì)象一開(kāi)始就是被初始化為代理對(duì)象,然后在執(zhí)行它的@Value的屬性的get操作的時(shí)候,會(huì)進(jìn)入代理方法,代理方法里會(huì)去獲取Target,這里就會(huì)觸發(fā) org.springframework.cloud.context.scope.GenericScope#get
public?Object?getBean()?{
?if?(this.bean?==?null)?{
??synchronized?(this.name)?{
???if?(this.bean?==?null)?{
???????//?因?yàn)閎ean為空,所以會(huì)觸發(fā)一次bean的重新初始化,走了一遍生命周期流程所以配置又回來(lái)了
????this.bean?=?this.objectFactory.getObject();
???}
??}
?}
?return?this.bean;
}
踩坑
上面的分析簡(jiǎn)單分析到那里,那么在使用這種配置自動(dòng)刷新機(jī)制有什么坑呢?
使用@RefreshScople的對(duì)象,如果把配置中心的某一行屬性刪掉,那么對(duì)應(yīng)的bean對(duì)應(yīng)的屬性會(huì)變?yōu)閚ull,但是使用@ConfigaruationProperties的對(duì)象則不會(huì),為什么呢?因?yàn)榍罢呤钦麄€(gè)bean重新走了一遍生命流程,但是后者只會(huì)執(zhí)行init方法
不管使用@RefreshScople和@ConfigaruationProperties都不應(yīng)該在destory和init方法中執(zhí)行過(guò)重的邏輯,前者會(huì)影響服務(wù)的可用性,在高并發(fā)下會(huì)阻塞太多數(shù)的請(qǐng)求。后者會(huì)影響配置刷新的時(shí)延性
粉絲福利:Java從入門到入土學(xué)習(xí)路線圖
??????

??長(zhǎng)按上方微信二維碼?2 秒
感謝點(diǎn)贊支持下哈?
