Nacos 学习笔记之服务启动加载配置

nacos 作为配置中心使用的 jar 包版本:

<dependency>

   <groupId>com.alibaba.cloud</groupId>

   <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>

   <version>2.2.6.RELEASE</version>

</dependency>

里面使用的nacos-client 版本是:

<dependency>

   <groupId>com.alibaba.nacos</groupId>

   <artifactId>nacos-client</artifactId>

   <version>1.4.2</version>

</dependency>

SpringBoot 项目启动加载 Nacos 配置过程:

SpringApplication 类的 prepareContext 方法 ---> applyInitializers(context)

---> PropertySourceBootstrapConfiguration.java 的 initialize(ConfigurableApplicationContext applicationContext) 方法。

在遍历 this.propertySourceLocators 中,会执行 NacosPropertySourceLocator实现的

locate(Environment env) 方法开始正式地获取 nacos 配置了。

NacosPropertySourceLocator 类

主要看 locate(Environment env) 中的 loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env) 这行代码。

private void loadApplicationConfiguration(

CompositePropertySource compositePropertySource, String dataIdPrefix,

NacosConfigProperties properties, Environment environment) {

String fileExtension = properties.getFileExtension();

String nacosGroup = properties.getGroup();

// load directly once by default

               // 直接加载dataId,比如 config

loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,

fileExtension, true);

// load with suffix, which have a higher priority than the default

               // 加载带文件后缀的dataId,优先级更高, 比如 config.yml

loadNacosDataIfPresent(compositePropertySource,

dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);

// Loaded with profile, which have a higher priority than the suffix

               // 加载带环境profile的dataId配置,优先级最高, 比如 config-test.yml

for (String profile : environment.getActiveProfiles()) {

String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;

loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,

fileExtension, true);

}



}

后面会进到 NacosPropertySourceBuilder 类的 build 方法:

NacosPropertySource build(String dataId, String group, String fileExtension,

boolean isRefreshable) {

    // 加载 nacos 配置

List<PropertySource<?>> propertySources = loadNacosData(dataId, group,

fileExtension);

NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources,

group, dataId, new Date(), isRefreshable);

NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);

return nacosPropertySource;

}

loadNacosData 方法:

private List<PropertySource<?>> 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 Collections.emptyList();

}

if (log.isDebugEnabled()) {

log.debug(String.format(

"Loading nacos data, dataId: '%s', group: '%s', data: %s", dataId,

group, data));

}

   //拿到nacos配置的数据,按照其格式解析成Spring中的 PropertySource,

   //这样就可以使用 @Value 注解获取使用配置项了

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();

}

configService 是nacos-client jar 包中实现 ConfigService接口的 NacosConfigService 类实现的。

NacosConfigService 类

@Override

   public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {

       return getConfigInner(namespace, dataId, group, timeoutMs);

   }

核心的 getConfigInner 方法,真正请求获取 nacos 配置的地方:

private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {

        group = blank2defaultGroup(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);

           String encryptedDataKey = LocalEncryptedDataKeyProcessor

                   .getEncryptDataKeyFailover(agent.getName(), dataId, group, tenant);

            cr.setEncryptedDataKey(encryptedDataKey);

            configFilterChainManager.doFilter(null, cr);

            content = cr.getContent();

           return content;

       }

       

       try {

           ConfigResponse response = worker.getServerConfig(dataId, group, tenant, timeoutMs);

            cr.setContent(response.getContent());

            cr.setEncryptedDataKey(response.getEncryptedDataKey());

           

            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);

       String encryptedDataKey = LocalEncryptedDataKeyProcessor

               .getEncryptDataKeyFailover(agent.getName(), dataId, group, tenant);

        cr.setEncryptedDataKey(encryptedDataKey);

        configFilterChainManager.doFilter(null, cr);

        content = cr.getContent();

       return content;

   }

可以看到,程序中优先使用的Nacos本地文件中的配置,如果本地文件配置为空才会通过http请求获取nacos服务端配置的数据。

本地文件路径:\nacos\config\.......

文件路径参照 LocalConfigInfoProcessor 类的 getFailoverFile 方法。

ClientWorker 类

public ConfigResponse getServerConfig(String dataId, String group, String tenant, long readTimeout)

           throws NacosException {

       ConfigResponse configResponse = new ConfigResponse();

       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:

               // 如果http请求返回的状态码是 200,保存快照配置数据到本地文件中去

               LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, result.getData());

                configResponse.setContent(result.getData());

               String configType;

               if (result.getHeader().getValue(CONFIG_TYPE) != null) {

                    configType = result.getHeader().getValue(CONFIG_TYPE);

               } else {

                    configType = ConfigType.TEXT.getType();

               }

                configResponse.setConfigType(configType);

               String encryptedDataKey = result.getHeader().getValue(ENCRYPTED_DATA_KEY);

               LocalEncryptedDataKeyProcessor

                       .saveEncryptDataKeySnapshot(agent.getName(), dataId, group, tenant, encryptedDataKey);

                configResponse.setEncryptedDataKey(encryptedDataKey);

               return configResponse;

           case HttpURLConnection.HTTP_NOT_FOUND:

               LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, null);

               LocalEncryptedDataKeyProcessor.saveEncryptDataKeySnapshot(agent.getName(), dataId, group, tenant, null);

               return configResponse;

           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);

           }

       }

   }

注意:

需要注意,nacos 优先从本地文件中获取的配置对应的文件路径和从nacos服务端获取到配置保存到本地的快照文件路径是不一样的。

对应 LocalConfigInfoProcessor 类中的两个不同方法:

//默认优先从本地文件中获取的文件路径

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);

   }

   

   //从服务端拉到配置数据后写到本地的快照文件路径

   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);

   }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值