写点什么

Nacos 配置中心之加载配置

作者:周杰伦本人
  • 2022 年 8 月 01 日
  • 本文字数:7079 字

    阅读完需:约 23 分钟

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("springApplicationArguments", applicationArguments);   if (printedBanner != null) {      beanFactory.registerSingleton("springBootBanner", printedBanner);   }   if (beanFactory instanceof DefaultListableBeanFactory) {      ((DefaultListableBeanFactory) beanFactory)            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);   }   // Load the sources   Set<Object> sources = getAllSources();   Assert.notEmpty(sources, "Sources must not be empty");   load(context, sources.toArray(new Object[0]));   listeners.contextLoaded(context);}
复制代码


  1. 调用 applyInitializers()方法

  2. 注册打印 banner 图的单例 bean

  3. 加载资源


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

applyInitializers()方法

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


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

PropertySourceBootstrapConfiguration 的 initialize()方法

@Overridepublic 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("Located property source: " + source);      composite.addPropertySource(source);      empty = false;   }   if (!empty) {      MutablePropertySources propertySources = environment.getPropertySources();      String logConfig = environment.resolvePlaceholders("${logging.config:}");      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()方法

@Overridepublic PropertySource<?> locate(Environment env) {
ConfigService configService = nacosConfigProperties.configServiceInstance();
if (null == configService) { log.warn("no instance of config service found, can't load config from nacos"); 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("spring.application.name"); }
CompositePropertySource composite = new CompositePropertySource( NACOS_PROPERTY_SOURCE_NAME);
loadSharedConfiguration(composite); loadExtConfiguration(composite); loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
return composite;}
复制代码


  1. 初始化 ConfigService 对象,ConfigService 是 Nacos 客户端提供的用于访问实现配置中心基本操作的类

  2. 如果为空打印支持没有 ConfigService 实例,不能加载配置,返回空

  3. 如果不为空,按照顺序分别加载共享配置、扩展配置、应用名称对应的配置。


进入 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(               "Ignore the empty nacos configuration and get it based on dataId[{}] & group[{}]",               dataId, group);         return EMPTY_PROPERTIES;      }      log.info(String.format(            "Loading nacos data, dataId: '%s', group: '%s', data: %s", dataId,            group, data));
Properties properties = NacosDataParserHandler.getInstance() .parseNacosData(data, fileExtension); return properties == null ? EMPTY_PROPERTIES : properties; } 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_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("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}, config={}", 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("[{}] [get-config] get from server error, dataId={}, group={}, tenant={}, msg={}", agent.getName(), dataId, group, tenant, ioe.toString()); }
LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}", 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("dataId", dataId, "group", group); } else { params = Arrays.asList("dataId", dataId, "group", group, "tenant", tenant); } //发起请求 result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout); } catch (IOException e) { String message = String.format( "[%s] [sub-server] get server config exception, dataId=%s, group=%s, tenant=%s", 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( "[{}] [sub-server-error] get server config being modified concurrently, dataId={}, group={}, " + "tenant={}", agent.getName(), dataId, group, tenant); throw new NacosException(NacosException.CONFLICT, "data being modified, dataId=" + dataId + ",group=" + group + ",tenant=" + tenant); } case HttpURLConnection.HTTP_FORBIDDEN: { LOGGER.error("[{}] [sub-server-error] no right, dataId={}, group={}, tenant={}", agent.getName(), dataId, group, tenant); throw new NacosException(result.code, result.content); } default: { LOGGER.error("[{}] [sub-server-error] dataId={}, group={}, tenant={}, code={}", agent.getName(), dataId, group, tenant, result.code); throw new NacosException(result.code, "http error, code=" + result.code + ",dataId=" + dataId + ",group=" + group + ",tenant=" + 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("dataId") String dataId, @RequestParam("group") String group,        @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant,        @RequestParam(value = "tag", required = false) String tag)        throws IOException, ServletException, NacosException {    // check tenant    ParamUtils.checkTenant(tenant);    tenant = NamespaceUtil.processNamespaceParameter(tenant);    // check params    ParamUtils.checkParam(dataId, group, "datumId", "content");    ParamUtils.checkParam(tag);        final String clientIp = RequestUtil.getRemoteIp(request);    String isNotify = request.getHeader("notify");    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 加载数据的流程,分析问题,定位问题,及时解决。

发布于: 刚刚阅读数: 3
用户头像

还未添加个人签名 2020.02.29 加入

公众号《盼盼小课堂》,多平台优质博主

评论

发布
暂无评论
Nacos配置中心之加载配置_8月月更_周杰伦本人_InfoQ写作社区