当前位置:网站首页>Nacos配置中心之加载配置

Nacos配置中心之加载配置

2022-08-01 22:59:00 InfoQ

Nacos配置中心之加载配置

一 客户端配置中心之加载配置

我们接着上个文章说的,Springboot启动的时候调用prep在reEnvironment()进行环境准备,prepareEnvironment()进行环境准备,在启动类执行完prepareEnvironment后,执行prepareContext进行刷新应用上下文件的准备代码如下:

public ConfigurableApplicationContext run(String... args) {
 StopWatch stopWatch = new StopWatch();
 stopWatch.start();
 ConfigurableApplicationContext context = null;
 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
 configureHeadlessProperty();
 SpringApplicationRunListeners listeners = getRunListeners(args);
 listeners.starting();
 try {
 ApplicationArguments applicationArguments = new DefaultApplicationArguments(
 args);
 //环境准备
 ConfigurableEnvironment environment = prepareEnvironment(listeners,
 applicationArguments);
 configureIgnoreBeanInfo(environment);
 Banner printedBanner = printBanner(environment);
 context = createApplicationContext();
 exceptionReporters = getSpringFactoriesInstances(
 SpringBootExceptionReporter.class,
 new Class[] { ConfigurableApplicationContext.class }, context);
 prepareContext(context, environment, listeners, applicationArguments,
 printedBanner);
 refreshContext(context);
 afterRefresh(context, applicationArguments);
 stopWatch.stop();
 if (this.logStartupInfo) {
 new StartupInfoLogger(this.mainApplicationClass)
 .logStarted(getApplicationLog(), stopWatch);
 }
 listeners.started(context);
 callRunners(context, applicationArguments);
 }
 catch (Throwable ex) {
 handleRunFailure(context, ex, exceptionReporters, listeners);
 throw new IllegalStateException(ex);
 }

 try {
 listeners.running(context);
 }
 catch (Throwable ex) {
 handleRunFailure(context, ex, exceptionReporters, null);
 throw new IllegalStateException(ex);
 }
 return context;
}

我们看一下prepareContext()准备上下文方法做了什么

prepareContext()方法

private void prepareContext(ConfigurableApplicationContext context,
 ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
 ApplicationArguments applicationArguments, Banner printedBanner) {
 context.setEnvironment(environment);
 postProcessApplicationContext(context);
 applyInitializers(context);
 listeners.contextPrepared(context);
 if (this.logStartupInfo) {
 logStartupInfo(context.getParent() == null);
 logStartupProfileInfo(context);
 }
 // Add boot specific singleton beans
 ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
 beanFactory.registerSingleton(&quot;springApplicationArguments&quot;, applicationArguments);
 if (printedBanner != null) {
 beanFactory.registerSingleton(&quot;springBootBanner&quot;, printedBanner);
 }
 if (beanFactory instanceof DefaultListableBeanFactory) {
 ((DefaultListableBeanFactory) beanFactory)
 .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
 }
 // Load the sources
 Set<Object> sources = getAllSources();
 Assert.notEmpty(sources, &quot;Sources must not be empty&quot;);
 load(context, sources.toArray(new Object[0]));
 listeners.contextLoaded(context);
}

  • 调用applyInitializers()方法
  • 注册打印banner图的单例bean
  • 加载资源

我们再看一下applyInitializers()方法是做什么的

applyInitializers()方法

@SuppressWarnings({ &quot;rawtypes&quot;, &quot;unchecked&quot; })
protected void applyInitializers(ConfigurableApplicationContext context) {
 for (ApplicationContextInitializer initializer : getInitializers()) {
 Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
 initializer.getClass(), ApplicationContextInitializer.class);
 Assert.isInstanceOf(requiredType, context, &quot;Unable to call initializer.&quot;);
 initializer.initialize(context);
 }
}

PropertySourceBootstrapConfiguration实现了ApplicationContextInitializer接口,所以会调用PropertySourceBootstrapConfiguration的initialize()方法

PropertySourceBootstrapConfiguration的initialize()方法

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
 CompositePropertySource composite = new CompositePropertySource(
 BOOTSTRAP_PROPERTY_SOURCE_NAME);
 AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
 boolean empty = true;
 ConfigurableEnvironment environment = applicationContext.getEnvironment();
 for (PropertySourceLocator locator : this.propertySourceLocators) {
 PropertySource<?> source = null;
 //加载
 source = locator.locate(environment);
 if (source == null) {
 continue;
 }
 logger.info(&quot;Located property source: &quot; + source);
 composite.addPropertySource(source);
 empty = false;
 }
 if (!empty) {
 MutablePropertySources propertySources = environment.getPropertySources();
 String logConfig = environment.resolvePlaceholders(&quot;${logging.config:}&quot;);
 LogFile logFile = LogFile.get(environment);
 if (propertySources.contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
 propertySources.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
 }
 insertPropertySources(propertySources, composite);
 reinitializeLoggingSystem(environment, logConfig, logFile);
 setLogLevels(applicationContext, environment);
 handleIncludedProfiles(environment);
 }
}

locator.locate(environment)方法会调用NacosPropertySourceLocator的locate方法,这就是加载配置的关键代码了

NacosPropertySourceLocator的locate()方法

@Override
public PropertySource<?> locate(Environment env) {

 ConfigService configService = nacosConfigProperties.configServiceInstance();

 if (null == configService) {
 log.warn(&quot;no instance of config service found, can't load config from nacos&quot;);
 return null;
 }
 long timeout = nacosConfigProperties.getTimeout();
 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(&quot;spring.application.name&quot;);
 }

 CompositePropertySource composite = new CompositePropertySource(
 NACOS_PROPERTY_SOURCE_NAME);

 loadSharedConfiguration(composite);
 loadExtConfiguration(composite);
 loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);

 return composite;
}

  • 初始化ConfigService对象,ConfigService是Nacos客户端提供的用于访问实现配置中心基本操作的类
  • 如果为空打印支持没有ConfigService实例,不能加载配置,返回空
  • 如果不为空,按照顺序分别加载共享配置、扩展配置、应用名称对应的配置。

进入loadApplicationConfiguration-》loadNacosDataIfPresent-》loadNacosPropertySource-》build-》loadNacosData

loadNacosData()方法

private Properties loadNacosData(String dataId, String group, String fileExtension) {
 String data = null;
 try {
 data = configService.getConfig(dataId, group, timeout);
 if (StringUtils.isEmpty(data)) {
 log.warn(
 &quot;Ignore the empty nacos configuration and get it based on dataId[{}] & group[{}]&quot;,
 dataId, group);
 return EMPTY_PROPERTIES;
 }
 log.info(String.format(
 &quot;Loading nacos data, dataId: '%s', group: '%s', data: %s&quot;, dataId,
 group, data));

 Properties properties = NacosDataParserHandler.getInstance()
 .parseNacosData(data, fileExtension);
 return properties == null ? EMPTY_PROPERTIES : properties;
 }
 catch (NacosException e) {
 log.error(&quot;get data from Nacos error,dataId:{}, &quot;, dataId, e);
 }
 catch (Exception e) {
 log.error(&quot;parse data from Nacos error,dataId:{},data:{},&quot;, dataId, data, e);
 }
 return EMPTY_PROPERTIES;
}

configService.getConfig方法从Nacos配置中心上加载配置进行填充

private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
 group = null2defaultGroup(group);
 ParamUtils.checkKeyParam(dataId, group);
 ConfigResponse cr = new ConfigResponse();

 cr.setDataId(dataId);
 cr.setTenant(tenant);
 cr.setGroup(group);

 // 优先使用本地配置
 String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
 if (content != null) {
 LOGGER.warn(&quot;[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}, config={}&quot;, agent.getName(),
 dataId, group, tenant, ContentUtils.truncateContent(content));
 cr.setContent(content);
 configFilterChainManager.doFilter(null, cr);
 content = cr.getContent();
 return content;
 }

 try {
 content = worker.getServerConfig(dataId, group, tenant, timeoutMs);

 cr.setContent(content);

 configFilterChainManager.doFilter(null, cr);
 content = cr.getContent();

 return content;
 } catch (NacosException ioe) {
 if (NacosException.NO_RIGHT == ioe.getErrCode()) {
 throw ioe;
 }
 LOGGER.warn(&quot;[{}] [get-config] get from server error, dataId={}, group={}, tenant={}, msg={}&quot;,
 agent.getName(), dataId, group, tenant, ioe.toString());
 }

 LOGGER.warn(&quot;[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}&quot;, agent.getName(),
 dataId, group, tenant, ContentUtils.truncateContent(content));
 content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);
 cr.setContent(content);
 configFilterChainManager.doFilter(null, cr);
 content = cr.getContent();
 return content;
}

方法中优先加载本地的配置,如果不为空就返回结果,否则这里又会调用ClientWork的getServerConfig()方法获取内容然后返回结果

ClientWork的getServerConfig()方法

public String getServerConfig(String dataId, String group, String tenant, long readTimeout)
 throws NacosException {
 if (StringUtils.isBlank(group)) {
 group = Constants.DEFAULT_GROUP;
 }

 HttpResult result = null;
 try {
 List<String> params = null;
 if (StringUtils.isBlank(tenant)) {
 params = Arrays.asList(&quot;dataId&quot;, dataId, &quot;group&quot;, group);
 } else {
 params = Arrays.asList(&quot;dataId&quot;, dataId, &quot;group&quot;, group, &quot;tenant&quot;, tenant);
 }
 //发起请求
 result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout);
 } catch (IOException e) {
 String message = String.format(
 &quot;[%s] [sub-server] get server config exception, dataId=%s, group=%s, tenant=%s&quot;, agent.getName(),
 dataId, group, tenant);
 LOGGER.error(message, e);
 throw new NacosException(NacosException.SERVER_ERROR, e);
 }

 switch (result.code) {
 case HttpURLConnection.HTTP_OK:
 LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, result.content);
 return result.content;
 case HttpURLConnection.HTTP_NOT_FOUND:
 LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, null);
 return null;
 case HttpURLConnection.HTTP_CONFLICT: {
 LOGGER.error(
 &quot;[{}] [sub-server-error] get server config being modified concurrently, dataId={}, group={}, &quot;
 + &quot;tenant={}&quot;, agent.getName(), dataId, group, tenant);
 throw new NacosException(NacosException.CONFLICT,
 &quot;data being modified, dataId=&quot; + dataId + &quot;,group=&quot; + group + &quot;,tenant=&quot; + tenant);
 }
 case HttpURLConnection.HTTP_FORBIDDEN: {
 LOGGER.error(&quot;[{}] [sub-server-error] no right, dataId={}, group={}, tenant={}&quot;, agent.getName(), dataId,
 group, tenant);
 throw new NacosException(result.code, result.content);
 }
 default: {
 LOGGER.error(&quot;[{}] [sub-server-error] dataId={}, group={}, tenant={}, code={}&quot;, agent.getName(), dataId,
 group, tenant, result.code);
 throw new NacosException(result.code,
 &quot;http error, code=&quot; + result.code + &quot;,dataId=&quot; + dataId + &quot;,group=&quot; + group + &quot;,tenant=&quot; + tenant);
 }
 }
}

现在水落石出了,这里调用客户端向服务端发送请求,agent.httpGet()发起请求,请求路径:/v1/cs/configs

二 服务端/v1/cs/configs接口的处理

服务端在nacos.config包下,
ConfigController
类的getConfig()方法

@GetMapping
@Secured(action = ActionTypes.READ, signType = SignType.CONFIG)
public void getConfig(HttpServletRequest request, HttpServletResponse response,
 @RequestParam(&quot;dataId&quot;) String dataId, @RequestParam(&quot;group&quot;) String group,
 @RequestParam(value = &quot;tenant&quot;, required = false, defaultValue = StringUtils.EMPTY) String tenant,
 @RequestParam(value = &quot;tag&quot;, required = false) String tag)
 throws IOException, ServletException, NacosException {
 // check tenant
 ParamUtils.checkTenant(tenant);
 tenant = NamespaceUtil.processNamespaceParameter(tenant);
 // check params
 ParamUtils.checkParam(dataId, group, &quot;datumId&quot;, &quot;content&quot;);
 ParamUtils.checkParam(tag);
 
 final String clientIp = RequestUtil.getRemoteIp(request);
 String isNotify = request.getHeader(&quot;notify&quot;);
 inner.doGetConfig(request, response, dataId, group, tenant, tag, isNotify, clientIp);
}

调用了
ConfigServletInner
的doGetConfig()方法,调用
tryConfigReadLock()
方法加入读锁,查看缓存中有没有nacos中配置的key 有的话就加锁成功了,加锁成功后调用ConfigCacheService.getContentCache()获取CacheItem实例,然后判断,然后根据配置信息等选择从数据库取数据还是获取本地文件,然后返回文件内容返回给客户端

总结

启动类执行完prepareEnvironment后,执行prepareContext进行刷新应用上下文件的准备,调用applyInitializers,调用NacosPropertySourceLocator的locate方法,初始化ConfigService对象,按照顺序分别加载共享配置、扩展配置、应用名称对应的配置,进入loadNacosData方法,然后configService.getConfig方法从Nacos配置中心上加载配置进行填充。

这就是nacos初始化的大体流程,如果我们在工作中遇到获取nacos数据获取不到的时候,我们可以试着跟踪一下nacos加载数据的流程,分析问题,定位问题,及时解决。
原网站

版权声明
本文为[InfoQ]所创,转载请带上原文链接,感谢
https://xie.infoq.cn/article/283ba87c2d052e7797dd5e0d6