1

After understanding the basic principles of the above Environment, how to load the configuration from the remote server into the Spring Environment.

NacosPropertySourceLocator

Following the previous analysis idea, we naturally found the implementation class of PropertySourceLocator, and found that in addition to our custom GpJsonPropertySourceLocator , there is another implementation class NacosPropertySourceLocator .

So, directly look at the NacosPropertySourceLocator method in locate , the code is as follows.

public PropertySource<?> locate(Environment env) {
    this.nacosConfigProperties.setEnvironment(env);
    ConfigService configService = this.nacosConfigManager.getConfigService();
    if (null == configService) {
        log.warn("no instance of config service found, can't load config from nacos");
        return null;
    } else {
        //获取客户端配置的超时时间
        long timeout = (long)this.nacosConfigProperties.getTimeout();
        this.nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout);
        //获取name属性,
        String name = this.nacosConfigProperties.getName();
        //在Spring Cloud中,默认的name=spring.application.name。
        String dataIdPrefix = this.nacosConfigProperties.getPrefix();
        if (StringUtils.isEmpty(dataIdPrefix)) {
            dataIdPrefix = name;
        }

        if (StringUtils.isEmpty(dataIdPrefix)) {
            dataIdPrefix = env.getProperty("spring.application.name"); //获取spring.application.name,赋值给dataIdPrefix
        }
       //创建一个Composite属性源,可以包含多个PropertySource
        CompositePropertySource composite = new CompositePropertySource("NACOS");
        this.loadSharedConfiguration(composite);   //加载共享配置 
         //加载扩展配置
        loadExtConfiguration(composite);
        //加载自身配置
        loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
        return composite;
    }
}

The implementation of the above code is not difficult to understand

  1. Get the configuration properties of the nacos client and generate dataId (this is very important, to locate the configuration of nacos)
  2. Call three methods respectively to load the configuration property source and save it to the composite composite property source

loadApplicationConfiguration

We can ignore the method of loading the shared configuration and extending the configuration. In the end, we essentially go to the remote service to read the configuration, but the parameters passed in are different.

  • fileExtension, which represents the extension of the configuration file
  • nacosGroup means grouping
  • Load config with dataid=project name
  • Load the configuration of dataid=project name+extension
  • Traverse the activation point (profile) of the current configuration, and load the dataid configuration with the profile in a loop
private void loadApplicationConfiguration(
    CompositePropertySource compositePropertySource, String dataIdPrefix,
    NacosConfigProperties properties, Environment environment) {
    String fileExtension = properties.getFileExtension();  //默认的扩展名为: properties
    String nacosGroup = properties.getGroup(); //获取group
    //加载`dataid=项目名称`的配置
    loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
                           fileExtension, true);
    //加载`dataid=项目名称+扩展名`的配置
    loadNacosDataIfPresent(compositePropertySource,
                           dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
    // 遍历profile(可以有多个),根据profile加载配置
    for (String profile : environment.getActiveProfiles()) {
        //此时的dataId=${spring.application.name}.${profile}.${fileExtension}
        String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
        loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
                               fileExtension, true);
    }

}

loadNacosDataIfPresent

Call loadNacosPropertySource to load the existing configuration information.

Save the loaded configuration properties to the CompositePropertySource.

private void loadNacosDataIfPresent(final CompositePropertySource composite,
      final String dataId, final String group, String fileExtension,
      boolean isRefreshable) {
    //如果dataId为空,或者group为空,则直接跳过
   if (null == dataId || dataId.trim().length() < 1) {
      return;
   }
   if (null == group || group.trim().length() < 1) {
      return;
   }
    //从nacos中获取属性源
   NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
         fileExtension, isRefreshable);
    //把属性源保存到compositePropertySource中
   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);
      }
   }
    //构造器从配置中心获取数据
   return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
         isRefreshable);
}

NacosPropertySourceBuilder.build

NacosPropertySource build(String dataId, String group, String fileExtension,
      boolean isRefreshable) {
        //调用loadNacosData加载远程数据
   List<PropertySource<?>> propertySources = loadNacosData(dataId, group,
         fileExtension);
    //构造NacosPropertySource(这个是Nacos自定义扩展的PropertySource,和我们前面演示的自定义PropertySource类似)。
//    相当于把从远程服务器获取的数据保存到NacosPropertySource中。
   NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources,
         group, dataId, new Date(), isRefreshable);
    //把属性缓存到本地缓存
   NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
   return nacosPropertySource;
}

NacosPropertySourceBuilder.loadNacosData

This method is the realization of connecting to a remote server to obtain configuration data. The key code is configService.getConfig

private List<PropertySource<?>> loadNacosData(String dataId, String group,
      String fileExtension) {
   String data = null;
   try {
      data = configService.getConfig(dataId, group, timeout); //加载Nacos配置数据
      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));
      }
       //对加载的数据进行解析,保存到List<PropertySource>集合。
      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();
}

Staged summary

Through the above analysis, we know the key path when Spring Cloud integrates Nacos, and know that at startup, Spring Cloud will load dynamic data from Nacos Server and save it to the Environment collection.

This enables automatic injection of dynamic configuration.

Data loading process of Nacos client

The final loading of configuration data is based on configService.getConfig , the SDK provided by Nacos.

public String getConfig(String dataId, String group, long timeoutMs) throws NacosException
Tutorial on using Nacos SDK: https://nacos.io/zh-cn/docs/sdk.html

That is to say, our source code analysis will directly enter the category of Nacos.

NacosConfigService.getConfig

@Override
public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
    return getConfigInner(namespace, dataId, group, timeoutMs);
}
private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
    group = blank2defaultGroup(group); //获取group,如果为空,则为default-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); //把内容设置到cr中。
        //获取容灾配置的encryptedDataKey
        String encryptedDataKey = LocalEncryptedDataKeyProcessor
                .getEncryptDataKeyFailover(agent.getName(), dataId, group, tenant);
        cr.setEncryptedDataKey(encryptedDataKey); //保存到cr
        configFilterChainManager.doFilter(null, cr); //执行过滤(目前好像没有实现)
        content = cr.getContent(); //返回文件content
        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());
    }
    //如果出现NacosException,且不是403异常,则尝试通过本地的快照文件去获取配置进行返回。
    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;
}

Read configuration from local cache

By default, nacos first reads the file from the locally cached configuration: C:\Users\mayn\nacos\config\fixed-192.168.8.133_8848-6a382560-ed4c-414c-a5e2-9d72c48f1a0e_nacos

Returns the content data if the local cache content exists, otherwise returns null.

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

Read the file contents from the specified file directory.

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

ClientWorker.getServerConfig

ClientWorker, represents a work class of the client, which is responsible for interacting with the server.

public ConfigResponse getServerConfig(String dataId, String group, String tenant, long readTimeout)
        throws NacosException {
    ConfigResponse configResponse = new ConfigResponse();
    if (StringUtils.isBlank(group)) { //如果group为空,则返回默认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());
            configResponse.setContent(result.getData());
            String configType;  //配置文件的类型,如text、json、yaml等
            if (result.getHeader().getValue(CONFIG_TYPE) != null) {
                configType = result.getHeader().getValue(CONFIG_TYPE);
            } else {
                configType = ConfigType.TEXT.getType();
            }
            configResponse.setConfigType(configType); //设置到configResponse中,后续要根据文件类型实现不同解析策略
            //获取加密数据的key
            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: //如果返回404, 清空本地快照
            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);
        }
    }
}

ServerHttpAgent.httpGet

An implementation that initiates a remote request.

@Override
public HttpRestResult<String> httpGet(String path, Map<String, String> headers, Map<String, String> paramValues,
        String encode, long readTimeoutMs) throws Exception {
    final long endTime = System.currentTimeMillis() + readTimeoutMs;
    injectSecurityInfo(paramValues);  //注入安全信息
    String currentServerAddr = serverListMgr.getCurrentServerAddr();//获取当前服务器地址
    int maxRetry = this.maxRetry; //获取最大重试次数,默认3次
    //配置HttpClient的属性,默认的readTimeOut超时时间是3s
    HttpClientConfig httpConfig = HttpClientConfig.builder()
            .setReadTimeOutMillis(Long.valueOf(readTimeoutMs).intValue())
            .setConTimeOutMillis(ConfigHttpClientManager.getInstance().getConnectTimeoutOrDefault(100)).build();
    do {
       
        try {
            //设置header
            Header newHeaders = getSpasHeaders(paramValues, encode);
            if (headers != null) {
                newHeaders.addAll(headers);
            }
            //构建query查询条件
            Query query = Query.newInstance().initParams(paramValues);
            //发起http请求,http://192.168.8.133:8848/nacos/v1/cs/configs
            HttpRestResult<String> result = NACOS_RESTTEMPLATE
                    .get(getUrl(currentServerAddr, path), httpConfig, newHeaders, query, String.class);
            if (isFail(result)) { //如果请求失败,
                LOGGER.error("[NACOS ConnectException] currentServerAddr: {}, httpCode: {}",
                        serverListMgr.getCurrentServerAddr(), result.getCode());
            } else {
                // Update the currently available server addr
                serverListMgr.updateCurrentServerAddr(currentServerAddr);
                return result;
            }
        } catch (ConnectException connectException) {
            LOGGER.error("[NACOS ConnectException httpGet] currentServerAddr:{}, err : {}",
                    serverListMgr.getCurrentServerAddr(), connectException.getMessage());
        } catch (SocketTimeoutException socketTimeoutException) {
            LOGGER.error("[NACOS SocketTimeoutException httpGet] currentServerAddr:{}, err : {}",
                    serverListMgr.getCurrentServerAddr(), socketTimeoutException.getMessage());
        } catch (Exception ex) {
            LOGGER.error("[NACOS Exception httpGet] currentServerAddr: " + serverListMgr.getCurrentServerAddr(),
                    ex);
            throw ex;
        }
        //如果服务端列表有多个,并且当前请求失败,则尝试用下一个地址进行重试
        if (serverListMgr.getIterator().hasNext()) {
            currentServerAddr = serverListMgr.getIterator().next();
        } else {
            maxRetry--; //重试次数递减
            if (maxRetry < 0) {
                throw new ConnectException(
                        "[NACOS HTTP-GET] The maximum number of tolerable server reconnection errors has been reached");
            }
            serverListMgr.refreshCurrentServerAddr();
        }
        
    } while (System.currentTimeMillis() <= endTime);
    
    LOGGER.error("no available server");
    throw new ConnectException("no available server");
}

Nacos Server configuration acquisition

The client loads the configuration to the server, and the interface called is: /nacos/v1/cs/configs , so the interface is found in the source code of Nacos

Locate the method in ConfigController.getConfig in the Nacos source code, the code is as follows:

@GetMapping
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
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); //租户,也就是namespaceid
    // check params
    ParamUtils.checkParam(dataId, group, "datumId", "content"); //检查请求参数是否为空
    ParamUtils.checkParam(tag);
    
    final String clientIp = RequestUtil.getRemoteIp(request); //获取请求的ip
    inner.doGetConfig(request, response, dataId, group, tenant, tag, clientIp); //加载配置
}

inner.doGetConfig

public String doGetConfig(HttpServletRequest request, HttpServletResponse response, String dataId, String group,
        String tenant, String tag, String clientIp) throws IOException, ServletException {
    final String groupKey = GroupKey2.getKey(dataId, group, tenant);
    String autoTag = request.getHeader("Vipserver-Tag");
    String requestIpApp = RequestUtil.getAppName(request); //请求端的应用名称
   
    int lockResult = tryConfigReadLock(groupKey);  //尝试获取当前请求配置的读锁(避免读写冲突)
    
    final String requestIp = RequestUtil.getRemoteIp(request); //请求端的ip
    
    boolean isBeta = false;
    //lockResult>0 ,表示CacheItem(也就是缓存的配置项)不为空,并且已经加了读锁,意味着这个缓存数据不能被删除。
    //lockResult=0 ,表示cacheItem为空,不需要加读锁
    //lockResult=01 , 表示加锁失败,存在冲突。
    //下面这个if,就是针对这三种情况进行处理。
    if (lockResult > 0) {
        // LockResult > 0 means cacheItem is not null and other thread can`t delete this cacheItem
        FileInputStream fis = null;
        try {
            String md5 = Constants.NULL;
            long lastModified = 0L;
            //从本地缓存中,根据groupKey获取CacheItem
            CacheItem cacheItem = ConfigCacheService.getContentCache(groupKey);
            //判断是否是beta发布,也就是测试版本
            if (cacheItem.isBeta() && cacheItem.getIps4Beta().contains(clientIp)) {
                isBeta = true;
            }
            //获取配置文件的类型
            final String configType =
                    (null != cacheItem.getType()) ? cacheItem.getType() : FileTypeEnum.TEXT.getFileType();
            response.setHeader("Config-Type", configType);
            //返回文件类型的枚举对象
            FileTypeEnum fileTypeEnum = FileTypeEnum.getFileTypeEnumByFileExtensionOrFileType(configType);
            String contentTypeHeader = fileTypeEnum.getContentType();
            response.setHeader(HttpHeaderConsts.CONTENT_TYPE, contentTypeHeader);
            
            File file = null;
            ConfigInfoBase configInfoBase = null;
            PrintWriter out = null;
            if (isBeta) { //如果是测试配置
                md5 = cacheItem.getMd54Beta();
                lastModified = cacheItem.getLastModifiedTs4Beta();
                if (PropertyUtil.isDirectRead()) {
                    configInfoBase = persistService.findConfigInfo4Beta(dataId, group, tenant);
                } else {
                    file = DiskUtil.targetBetaFile(dataId, group, tenant); //从磁盘中获取文件,得到的是一个完整的File
                }
                response.setHeader("isBeta", "true");
            } else {
                if (StringUtils.isBlank(tag)) { //判断tag标签是否为空,tag对应的是nacos配置中心的标签选项
                    if (isUseTag(cacheItem, autoTag)) {
                        if (cacheItem.tagMd5 != null) {
                            md5 = cacheItem.tagMd5.get(autoTag);
                        }
                        if (cacheItem.tagLastModifiedTs != null) {
                            lastModified = cacheItem.tagLastModifiedTs.get(autoTag);
                        }
                        if (PropertyUtil.isDirectRead()) {
                            configInfoBase = persistService.findConfigInfo4Tag(dataId, group, tenant, autoTag);
                        } else {
                            file = DiskUtil.targetTagFile(dataId, group, tenant, autoTag);
                        }
                        
                        response.setHeader("Vipserver-Tag",
                                URLEncoder.encode(autoTag, StandardCharsets.UTF_8.displayName()));
                    } else {//直接走这个逻辑(默认不会配置tag属性)
                        md5 = cacheItem.getMd5(); //获取缓存的md5
                        lastModified = cacheItem.getLastModifiedTs(); //获取最后更新时间
                        if (PropertyUtil.isDirectRead()) {  //判断是否是stamdalone模式且使用的是derby数据库,如果是,则从derby数据库加载数据
                            configInfoBase = persistService.findConfigInfo(dataId, group, tenant);
                        } else {
                            //否则,如果是数据库或者集群模式,先从本地磁盘得到文件
                            file = DiskUtil.targetFile(dataId, group, tenant);
                        }
                        //如果本地磁盘文件为空,并且configInfoBase为空,则表示配置数据不存在,直接返回null
                        if (configInfoBase == null && fileNotExist(file)) {
                            // FIXME CacheItem
                            // No longer exists. It is impossible to simply calculate the push delayed. Here, simply record it as - 1.
                            ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1,
                                    ConfigTraceService.PULL_EVENT_NOTFOUND, -1, requestIp);
                            
                            // pullLog.info("[client-get] clientIp={}, {},
                            // no data",
                            // new Object[]{clientIp, groupKey});
                            
                            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                            response.getWriter().println("config data not exist");
                            return HttpServletResponse.SC_NOT_FOUND + "";
                        }
                    }
                } else {//如果tag不为空,说明配置文件设置了tag标签
                    if (cacheItem.tagMd5 != null) {
                        md5 = cacheItem.tagMd5.get(tag); 
                    }
                    if (cacheItem.tagLastModifiedTs != null) {
                        Long lm = cacheItem.tagLastModifiedTs.get(tag);
                        if (lm != null) {
                            lastModified = lm;
                        }
                    }
                    if (PropertyUtil.isDirectRead()) {
                        configInfoBase = persistService.findConfigInfo4Tag(dataId, group, tenant, tag);
                    } else {
                        file = DiskUtil.targetTagFile(dataId, group, tenant, tag);
                    }
                    if (configInfoBase == null && fileNotExist(file)) {
                        // FIXME CacheItem
                        // No longer exists. It is impossible to simply calculate the push delayed. Here, simply record it as - 1.
                        ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1,
                                ConfigTraceService.PULL_EVENT_NOTFOUND, -1, requestIp);
                        
                        // pullLog.info("[client-get] clientIp={}, {},
                        // no data",
                        // new Object[]{clientIp, groupKey});
                        
                        response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                        response.getWriter().println("config data not exist");
                        return HttpServletResponse.SC_NOT_FOUND + "";
                    }
                }
            }
            //把获取的数据结果设置到response中返回
            
            response.setHeader(Constants.CONTENT_MD5, md5);
            
            // Disable cache.
            response.setHeader("Pragma", "no-cache");
            response.setDateHeader("Expires", 0);
            response.setHeader("Cache-Control", "no-cache,no-store");
            if (PropertyUtil.isDirectRead()) {
                response.setDateHeader("Last-Modified", lastModified);
            } else {
                fis = new FileInputStream(file);
                response.setDateHeader("Last-Modified", file.lastModified());
            }
            //如果是单机模式,直接把数据写回到客户端
            if (PropertyUtil.isDirectRead()) {
                out = response.getWriter();
                out.print(configInfoBase.getContent());
                out.flush();
                out.close();
            } else {//否则,通过trasferTo
                fis.getChannel()
                        .transferTo(0L, fis.getChannel().size(), Channels.newChannel(response.getOutputStream()));
            }
            
            LogUtil.PULL_CHECK_LOG.warn("{}|{}|{}|{}", groupKey, requestIp, md5, TimeUtils.getCurrentTimeStr());
            
            final long delayed = System.currentTimeMillis() - lastModified;
            
            // TODO distinguish pull-get && push-get
            /*
             Otherwise, delayed cannot be used as the basis of push delay directly,
             because the delayed value of active get requests is very large.
             */
            ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, lastModified,
                    ConfigTraceService.PULL_EVENT_OK, delayed, requestIp);
            
        } finally { 
            releaseConfigReadLock(groupKey); //释放锁
            IoUtils.closeQuietly(fis);
        }
    } else if (lockResult == 0) { //说明缓存为空,
        
        // FIXME CacheItem No longer exists. It is impossible to simply calculate the push delayed. Here, simply record it as - 1.
        ConfigTraceService
                .logPullEvent(dataId, group, tenant, requestIpApp, -1, ConfigTraceService.PULL_EVENT_NOTFOUND, -1,
                        requestIp);
        
        response.setStatus(HttpServletResponse.SC_NOT_FOUND);
        response.getWriter().println("config data not exist");
        return HttpServletResponse.SC_NOT_FOUND + "";
        
    } else {//
        
        PULL_LOG.info("[client-get] clientIp={}, {}, get data during dump", clientIp, groupKey);
        
        response.setStatus(HttpServletResponse.SC_CONFLICT);
        response.getWriter().println("requested file is being modified, please try later.");
        return HttpServletResponse.SC_CONFLICT + "";
        
    }
    
    return HttpServletResponse.SC_OK + "";
}

persistService.findConfigInfo

Get data content from derby database, this is a basic data query operation.

@Override
public ConfigInfo findConfigInfo(final String dataId, final String group, final String tenant) {
    final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
    final String sql = "SELECT ID,data_id,group_id,tenant_id,app_name,content,md5,type FROM config_info "
            + " WHERE data_id=? AND group_id=? AND tenant_id=?";
    final Object[] args = new Object[] {dataId, group, tenantTmp};
    return databaseOperate.queryOne(sql, args, CONFIG_INFO_ROW_MAPPER);
    
}

DiskUtil.targetFile

Get the target file from the disk directory, and find the file in the specified directory directly according to dataId/group/tenant

public static File targetFile(String dataId, String group, String tenant) {
    File file = null;
    if (StringUtils.isBlank(tenant)) {
        file = new File(EnvUtil.getNacosHome(), BASE_DIR);
    } else {
        file = new File(EnvUtil.getNacosHome(), TENANT_BASE_DIR);
        file = new File(file, tenant);
    }
    file = new File(file, group);
    file = new File(file, dataId);
    return file;
}

So far, NacosPropertySourceLocator has completed the dynamic acquisition of configuration from Nacos Server and cached it locally, thus realizing the ability of Nacos dynamic configuration acquisition!

Copyright notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated. Reprint please indicate from Mic takes you to learn architecture!
If this article is helpful to you, please help to follow and like, your persistence is the driving force for my continuous creation. Welcome to follow the WeChat public account of the same name to get more technical dry goods!

跟着Mic学架构
810 声望1.1k 粉丝

《Spring Cloud Alibaba 微服务原理与实战》、《Java并发编程深度理解及实战》作者。 咕泡教育联合创始人,12年开发架构经验,对分布式微服务、高并发领域有非常丰富的实战经验。