阅读 70

Nacos配置中心1-配置文件的加载

0. 环境

  • nacos版本:1.4.1

  • Spring Cloud : 2020.0.2

  • Spring Boot :2.4.4

  • Spring Cloud alibaba: 2.2.5.RELEASE

  • Spring Cloud openFeign 2.2.2.RELEASE

测试代码:github.com/hsfxuebao/s…

1. 客户端初始化

image.png

@Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true) public class NacosConfigBootstrapConfiguration {    @Bean    @ConditionalOnMissingBean    public NacosConfigProperties nacosConfigProperties() {       return new NacosConfigProperties();    }    @Bean    @ConditionalOnMissingBean    public NacosConfigManager nacosConfigManager(          NacosConfigProperties nacosConfigProperties) {       return new NacosConfigManager(nacosConfigProperties);    }    @Bean    public NacosPropertySourceLocator nacosPropertySourceLocator(          NacosConfigManager nacosConfigManager) {       return new NacosPropertySourceLocator(nacosConfigManager);    } } 复制代码

NacosConfigManager 的构造方法中会调用 createConfigService 方法来创建 ConfigService 实例,内部调用工厂方法 ConfigFactory#createConfigService 通过反射实例化一个com.alibaba.nacos.client.config.NacosConfigService 的实例对象。

public static ConfigService createConfigService(Properties properties) throws NacosException {     try {         Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.config.NacosConfigService");         Constructor constructor = driverImplClass.getConstructor(Properties.class);         ConfigService vendorImpl = (ConfigService) constructor.newInstance(properties);         return vendorImpl;     } catch (Throwable e) {         throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);     } } 复制代码

NacosPropertySourceLocator 继承 PropertySourceLocator(PropertySourceLocator接口支持扩展自定义配置加载到 Spring Environment中)通过 locate 加载配置信息。

// SpringBoot在启动时会准备环境,此时会调用该方法。 // 该方法会从配置中心加载配置文件 @Override public PropertySource<?> locate(Environment env) {    // 将bootstrap.yml文件内容加载到内存    nacosConfigProperties.setEnvironment(env);    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();    nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,          timeout);    // 获取spring.cloud.nacos.config.name属性值,即要加载的配置文件的名称    String name = nacosConfigProperties.getName();    // 获取spring.cloud.nacos.config.prefix属性值,即要加载的配置文件的名称    String dataIdPrefix = nacosConfigProperties.getPrefix();    if (StringUtils.isEmpty(dataIdPrefix)) {       dataIdPrefix = name;    }    // 若没有设置name与prefix属性,则要加载的配置文件名称取spring.application.name属性值    if (StringUtils.isEmpty(dataIdPrefix)) {       dataIdPrefix = env.getProperty("spring.application.name");    }    CompositePropertySource composite = new CompositePropertySource(          NACOS_PROPERTY_SOURCE_NAME);    // todo 加载共享配置    // 1)首先加载本地配置    // 2)若没有,则加载远程配置    // 3)若没有,则加载本地快照配置    loadSharedConfiguration(composite);    // todo 加载扩展配置    loadExtConfiguration(composite);    // todo 加载自身配置(注意,其会加载三类配置文件)    loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);    return composite; } 复制代码

下面,分别分析加载 共享配置、扩展配置、自身配置。

2. 加载共享配置

// com.alibaba.cloud.nacos.client.NacosPropertySourceLocator#loadSharedConfiguration private void loadSharedConfiguration(       CompositePropertySource compositePropertySource) {    // 获取到所有共享配置文件    List<NacosConfigProperties.Config> sharedConfigs = nacosConfigProperties          .getSharedConfigs();    // 只要存在共享配置文件,则加载它们    if (!CollectionUtils.isEmpty(sharedConfigs)) {       // todo 检测所有共享配置文件是否具有dataId       checkConfiguration(sharedConfigs, "shared-configs");       // todo 加载所有共享配置文件       loadNacosConfiguration(compositePropertySource, sharedConfigs);    } } private void checkConfiguration(List<NacosConfigProperties.Config> configs,       String tips) {    for (int i = 0; i < configs.size(); i++) {       // 若配置文件没有dataId属性,则抛出异常       String dataId = configs.get(i).getDataId();       if (dataId == null || dataId.trim().length() == 0) {          throw new IllegalStateException(String.format(                "the [ spring.cloud.nacos.config.%s[%s] ] must give a dataId",                tips, i));       }    } } private void loadNacosConfiguration(final CompositePropertySource composite,       List<NacosConfigProperties.Config> configs) {    for (NacosConfigProperties.Config config : configs) {       // todo 加载当前遍历的配置文件       loadNacosDataIfPresent(composite, config.getDataId(), config.getGroup(),             NacosDataParserHandler.getInstance()                   .getFileExtension(config.getDataId()),             config.isRefresh());    } } 复制代码

主要通过 loadNacosDataIfPresent 读取配置信息, 其实我们可以通过参数看出,主要配置文件包含一下及部分: dataId, group, fileExtension

private void loadNacosDataIfPresent(final CompositePropertySource composite,       final String dataId, final String group, String fileExtension,       boolean isRefreshable) {    if (null == dataId || dataId.trim().length() < 1) {       return;    }    if (null == group || group.trim().length() < 1) {       return;    }    // todo 加载指定名称的配置文件    NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,          fileExtension, isRefreshable);    // 将加载的配置文件添加到composite中    this.addFirstPropertySource(composite, propertySource, false); } 复制代码

核心方法loadNacosPropertySource:

private NacosPropertySource loadNacosPropertySource(final String dataId,       final String group, String fileExtension, boolean isRefreshable) {    // 处理不能自动刷新的情况    if (NacosContextRefresher.getRefreshCount() != 0) {       if (!isRefreshable) {          return NacosPropertySourceRepository.getNacosPropertySource(dataId,                group);       }    }    // todo 处理配置文件会自动刷新的情况    return nacosPropertySourceBuilder.build(dataId, group, fileExtension,          isRefreshable); } NacosPropertySource build(String dataId, String group, String fileExtension,       boolean isRefreshable) {    // todo 加载配置文件    List<PropertySource<?>> propertySources = loadNacosData(dataId, group,          fileExtension);    NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources,          group, dataId, new Date(), isRefreshable);    NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);    return nacosPropertySource; } private List<PropertySource<?>> loadNacosData(String dataId, String group,       String fileExtension) {    String data = null;    try {       // todo 加载配置文件       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 Collections.emptyList();       }       if (log.isDebugEnabled()) {          log.debug(String.format(                "Loading nacos data, dataId: '%s', group: '%s', data: %s", dataId,                group, data));       }       // 将加载到的配置文件String解析为List       return NacosDataParserHandler.getInstance().parseNacosData(dataId, data,             fileExtension);    }    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 Collections.emptyList(); } @Override public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {     return getConfigInner(namespace, dataId, group, timeoutMs); } 复制代码

核心方法getConfigInner:

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);     // todo 优先使用本地配置     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 {         // todo 从远程读取         String[] ct = worker.getServerConfig(dataId, group, tenant, timeoutMs);         cr.setContent(ct[0]);         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));     // todo 快照配置     content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);     cr.setContent(content);     configFilterChainManager.doFilter(null, cr);     content = cr.getContent();     return content; } 复制代码

核心步骤:

  1. 首先,加载本地配置,如果有就直接返回

  2. 其次,去远程获取配置,如果有就直接返回

  3. 最后,加载快照配置,如果有就直接返回

2.1 加载本地配置

// com.alibaba.nacos.client.config.impl.LocalConfigInfoProcessor#getFailover public static String getFailover(String serverName, String dataId, String group, String tenant) {     File localPath = getFailoverFile(serverName, dataId, group, tenant);     if (!localPath.exists() || !localPath.isFile()) {         return null;     }          try {         return readFile(localPath);     } catch (IOException ioe) {         LOGGER.error("[" + serverName + "] get failover error, " + localPath, ioe);         return null;     } } static File getFailoverFile(String serverName, String dataId, String group, String tenant) {     File tmp = new File(LOCAL_SNAPSHOT_PATH, serverName + "_nacos");     tmp = new File(tmp, "data");     if (StringUtils.isBlank(tenant)) {         tmp = new File(tmp, "config-data");     } else {         tmp = new File(tmp, "config-data-tenant");         tmp = new File(tmp, tenant);     }     return new File(new File(tmp, group), dataId); } public static final String LOCAL_FILEROOT_PATH; public static final String LOCAL_SNAPSHOT_PATH; static {     LOCAL_FILEROOT_PATH =             System.getProperty("JM.LOG.PATH", System.getProperty("user.home")) + File.separator + "nacos"                     + File.separator + "config";     LOCAL_SNAPSHOT_PATH =             System.getProperty("JM.SNAPSHOT.PATH", System.getProperty("user.home")) + File.separator + "nacos"                     + File.separator + "config";     LOGGER.info("LOCAL_SNAPSHOT_PATH:{}", LOCAL_SNAPSHOT_PATH); } 复制代码

2.2 加载远程配置文件

// com.alibaba.nacos.client.config.impl.ClientWorker#getServerConfig public String[] getServerConfig(String dataId, String group, String tenant, long readTimeout)         throws NacosException {     String[] ct = new String[2];     if (StringUtils.isBlank(group)) {         group = Constants.DEFAULT_GROUP;     }     HttpRestResult<String> result = null;     try {         Map<String, String> params = new HashMap<String, String>(3);         if (StringUtils.isBlank(tenant)) {             params.put("dataId", dataId);             params.put("group", group);         } else {             params.put("dataId", dataId);             params.put("group", group);             params.put("tenant", tenant);         }         // 发送请求         result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout);     } catch (Exception ex) {         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, ex);         throw new NacosException(NacosException.SERVER_ERROR, ex);     }     switch (result.getCode()) {         case HttpURLConnection.HTTP_OK:             LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, result.getData());             ct[0] = result.getData();             if (result.getHeader().getValue(CONFIG_TYPE) != null) {                 ct[1] = result.getHeader().getValue(CONFIG_TYPE);             } else {                 ct[1] = ConfigType.TEXT.getType();             }             return ct;         case HttpURLConnection.HTTP_NOT_FOUND:             LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, null);             return ct;         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.getCode(), result.getMessage());         }         default: {             LOGGER.error("[{}] [sub-server-error]  dataId={}, group={}, tenant={}, code={}", agent.getName(),                     dataId, group, tenant, result.getCode());             throw new NacosException(result.getCode(),                     "http error, code=" + result.getCode() + ",dataId=" + dataId + ",group=" + group + ",tenant="                             + tenant);         }     } } 复制代码

agent 默认使用 MetricsHttpAgent 实现类。

2.3 加载快照文件

/**  * 获取本地缓存文件内容。NULL表示没有本地文件或抛出异常.  */  // com.alibaba.nacos.client.config.impl.LocalConfigInfoProcessor#getSnapshot public static String getSnapshot(String name, String dataId, String group, String tenant) {     if (!SnapShotSwitch.getIsSnapShot()) {         return null;     }     File file = getSnapshotFile(name, dataId, group, tenant);     if (!file.exists() || !file.isFile()) {         return null;     }          try {         return readFile(file);     } catch (IOException ioe) {         LOGGER.error("[" + name + "]+get snapshot error, " + file, ioe);         return null;     } } static File getSnapshotFile(String envName, String dataId, String group, String tenant) {     File tmp = new File(LOCAL_SNAPSHOT_PATH, envName + "_nacos");     if (StringUtils.isBlank(tenant)) {         tmp = new File(tmp, "snapshot");     } else {         tmp = new File(tmp, "snapshot-tenant");         tmp = new File(tmp, tenant);     }          return new File(new File(tmp, group), dataId); } public static final String LOCAL_FILEROOT_PATH; public static final String LOCAL_SNAPSHOT_PATH; static {     LOCAL_FILEROOT_PATH =             System.getProperty("JM.LOG.PATH", System.getProperty("user.home")) + File.separator + "nacos"                     + File.separator + "config";     LOCAL_SNAPSHOT_PATH =             System.getProperty("JM.SNAPSHOT.PATH", System.getProperty("user.home")) + File.separator + "nacos"                     + File.separator + "config";     LOGGER.info("LOCAL_SNAPSHOT_PATH:{}", LOCAL_SNAPSHOT_PATH); } 复制代码

2.4 小节

获取配置的主要方法是 NacosConfigService 类的 getConfigInner 方法,通常情况下该方法直接从本地文件中取得配置的值,如果本地文件不存在或者内容为空,则再通过 HTTP GET 方法从远端拉取配置,并保存到本地快照中。

image.png

当通过 HTTP 获取远端配置时,Nacos 提供了两种熔断策略,一是超时时间,二是最大重试次数,默认重试三次。

3. 加载扩展配置

// com.alibaba.cloud.nacos.client.NacosPropertySourceLocator#loadExtConfiguration private void loadExtConfiguration(CompositePropertySource compositePropertySource) {    List<NacosConfigProperties.Config> extConfigs = nacosConfigProperties          .getExtensionConfigs();    if (!CollectionUtils.isEmpty(extConfigs)) {       checkConfiguration(extConfigs, "extension-configs");       // todo        loadNacosConfiguration(compositePropertySource, extConfigs);    } } 复制代码

loadNacosConfiguration和第2节分析的一样

4. 加载应用自身配置

// com.alibaba.cloud.nacos.client.NacosPropertySourceLocator#loadApplicationConfiguration private void loadApplicationConfiguration(       CompositePropertySource compositePropertySource, String dataIdPrefix,       NacosConfigProperties properties, Environment environment) {    // 获取配置文件扩展名    String fileExtension = properties.getFileExtension();    // 获取配置文件所在的groupId    String nacosGroup = properties.getGroup();    // load directly once by default    // todo 加载仅有文件名称,没有扩展名的配置文件    loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,          fileExtension, true);    // load with suffix, which have a higher priority than the default    // todo 加载有文件名称,也有扩展名的配置文件    loadNacosDataIfPresent(compositePropertySource,          dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);    // Loaded with profile, which have a higher priority than the suffix    // todo 加载有文件名称,有扩展名,且还包含多环境选择profile的配置文件    for (String profile : environment.getActiveProfiles()) {       String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;       loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,             fileExtension, true);    } } 复制代码

加载自身文件主要有三类:

  • 加载仅有文件名称,没有扩展名的配置文件

  • 加载有文件名称,也有扩展名的配置文件

  • 加载有文件名称,有扩展名,且还包含多环境选择profile的配置文件

5. 总结

以上三类配置文件的加载顺序为, 共享配置 -> 扩展配置 -> 当前应用自身配置,如果存在相同属性配置了不同属性值,则后加载的会将先加载的给覆盖。即优先级为: 共享配置 < 扩展配置 < 应用自身配置

对于每种配置文件的加载过程,又存在三种可用选择:在应用本地存在同名配置,远程 配置中心存在同名配置,本地磁盘快照 snapshot 中存在同名配置。这三种配置的优先级为: 本地配置 > 远程配置 > 快照配置只要前面的加载到了,后面的就不再加载。

若要在应用本地存放同名配置,则需要存放到当前用户主目录下的 nacos\config\fixed-localhost_8848_nacos\data\config-data\{groupId}目录中。

若开启了配置的快照功能,则默认会将快照记录在当前用户主目录下的 nacos\config\fixed-localhost_8848_nacos\snapshot\{groupId}目录中。

方法调用流程图如下: image.png


作者:hsfxuebao
链接:https://juejin.cn/post/7170244688329474079


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐