<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SpringCloud配置刷新機(jī)制的簡(jiǎn)單分析[nacos為例子]

          共 11036字,需瀏覽 23分鐘

           ·

          2021-02-04 09:32

          點(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

          1. 本文主要分為SpringCloud Nacos的設(shè)計(jì)思路

          2. 簡(jiǎn)單分析一下觸發(fā)刷新事件后發(fā)生的過(guò)程以及一些踩坑經(jīng)驗(yàn)

          org.springframework.cloud.bootstrap.config.PropertySourceLocator

          1. 這是一個(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);
          1. com.alibaba.cloud.nacos.client.NacosPropertySourceLocator

          • 該類為nacos實(shí)現(xiàn)的配置發(fā)現(xiàn)類

          1. 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

          1. 源碼解析

          @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;
          }
          1. 每次nacos檢查到配置更新的時(shí)候就會(huì)觸發(fā)上下文配置刷新,就會(huì)調(diào)取locate這個(gè)方法

          org.springframework.cloud.endpoint.event.RefreshEvent

          1. 該事件為spring cloud內(nèi)置的事件,用于刷新配置

          com.alibaba.cloud.nacos.refresh.NacosRefreshHistory

          1. 該類用于nacos刷新歷史的存放,用來(lái)保存每次拉取的配置的md5值,用于比較配置是否需要刷新

          com.alibaba.cloud.nacos.refresh.NacosContextRefresher

          1. 該類是Nacos用來(lái)管理一些內(nèi)部監(jiān)聽(tīng)器的,主要是配置刷新的時(shí)候可以出發(fā)回調(diào),并且發(fā)出spring cloud上下文的配置刷新事件

          com.alibaba.cloud.nacos.NacosPropertySourceRepository

          1. 該類是nacos用來(lái)保存拉取到的數(shù)據(jù)的

          2. 流程:

          • 刷新器檢查到配置更新,保存到NacosPropertySourceRepository

          • 發(fā)起刷新事件

          • locate執(zhí)行,直接讀取NacosPropertySourceRepository

          com.alibaba.nacos.client.config.NacosConfigService

          1. 該類是nacos的主要刷新配置服務(wù)類

          2. 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);
          1. 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

          1. 該包提供了很多文件類型的轉(zhuǎn)換器

          2. 加載數(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;
          }
          1. 數(shù)據(jù)會(huì)變成key value的形式,然后轉(zhuǎn)換成PropertySource

          如何配置一個(gè)啟動(dòng)配置類

          1. 由于配置上下文是屬于SpringCloud管理的,所以本次的注入跟以往SpringBoot不一樣

          org.springframework.cloud.bootstrap.BootstrapConfiguration=\
          com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration
          1. 如何在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;
          }

          1. 該類是對(duì)RefreshEvent監(jiān)聽(tīng)的處理

          2. 直接定位到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
          1. 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());
          }

          1. 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;
          }

          踩坑

          1. 上面的分析簡(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)贊支持下哈?

          瀏覽 111
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美日韩高清免费观看一区二区三区四区 | 国产婷婷五月综合亚洲 | 色五月网 | 在线播放国产一 | 91久久久成人视频免费 |