Source code analysis of Spring Cloud Nacos for dynamic configuration loading

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

NacosPropertySourceLocator

Following the above analysis ideas, we naturally went to find the implementation class of PropertySourceLocator and found that in addition to our customized GpJsonPropertySourceLocator, there is another implementation class NacosPropertySourceLocator

So, let's directly look at the locate method in NacosPropertySourceLocator. 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 {
        //Gets the timeout configured by the client
        long timeout = (long)this.nacosConfigProperties.getTimeout();
        this.nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout);
        //Get the name attribute,
        String name = this.nacosConfigProperties.getName();
        //In Spring Cloud, the default name is spring application. name. 
        String dataIdPrefix = this.nacosConfigProperties.getPrefix();
        if (StringUtils.isEmpty(dataIdPrefix)) {
            dataIdPrefix = name;
        }

        if (StringUtils.isEmpty(dataIdPrefix)) {
            dataIdPrefix = env.getProperty("spring.application.name"); //Get spring application. Name, assigned to dataIdPrefix
        }
       //Create a Composite property source that can contain multiple propertysources
        CompositePropertySource composite = new CompositePropertySource("NACOS");
        this.loadSharedConfiguration(composite);   //Load shared configuration 
         //Load extended configuration
        loadExtConfiguration(composite);
        //Load own configuration
        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 the dataId (this is very important to locate the nacos configuration)
  2. Call three methods respectively to load the configuration attribute source and save it to the composite composite attribute source

loadApplicationConfiguration

Regardless of the method of loading shared configuration and extended configuration, we can read the configuration from the remote service in essence, but the incoming parameters are different.

  • fileExtension, which indicates the extension of the configuration file
  • nacosGroup stands for grouping
  • Load configuration with dataid = project name
  • Load the configuration with dataid = project name + extension
  • Traverse the currently configured activation point (profile) and load the dataid configuration with profile circularly
private void loadApplicationConfiguration(
    CompositePropertySource compositePropertySource, String dataIdPrefix,
    NacosConfigProperties properties, Environment environment) {
    String fileExtension = properties.getFileExtension();  //The default extension is: properties
    String nacosGroup = properties.getGroup(); //Get group
    //Load the configuration of 'dataid = project name'
    loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
                           fileExtension, true);
    //Load the configuration of 'dataid = project name + extension'
    loadNacosDataIfPresent(compositePropertySource,
                           dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
    // Traverse the profile (there can be multiple), and load the configuration according to the 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 CompositePropertySource.

private void loadNacosDataIfPresent(final CompositePropertySource composite,
      final String dataId, final String group, String fileExtension,
      boolean isRefreshable) {
    //If the dataId is empty or the group is empty, skip directly
   if (null == dataId || dataId.trim().length() < 1) {
      return;
   }
   if (null == group || group.trim().length() < 1) {
      return;
   }
    //Get attribute source from nacos
   NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
         fileExtension, isRefreshable);
    //Save the property source to 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) { //Whether automatic refresh is supported, / / if automatic refresh configuration is not supported, it will be automatically obtained from the cache and returned (not loaded from the remote server)
         return NacosPropertySourceRepository.getNacosPropertySource(dataId,
               group);
      }
   }
    //The constructor obtains data from the configuration center
   return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
         isRefreshable);
}

NacosPropertySourceBuilder.build

NacosPropertySource build(String dataId, String group, String fileExtension,
      boolean isRefreshable) {
        //Call loadNacosData to load remote data
   List<PropertySource<?>> propertySources = loadNacosData(dataId, group,
         fileExtension);
    //Construct Nacos PropertySource (this is the PropertySource of Nacos custom extension, which is similar to the custom PropertySource we demonstrated earlier).
//    It is equivalent to saving the data obtained from the remote server to the NacosPropertySource.
   NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources,
         group, dataId, new Date(), isRefreshable);
    //Cache properties to local cache
   NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
   return nacosPropertySource;
}

NacosPropertySourceBuilder.loadNacosData

This method is to connect to the remote server to obtain the 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); //Load Nacos configuration data
      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));
      }
       //Parse the loaded data and save it to the list < propertysource > collection.
      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();
}

Phased summary

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

So as to realize the automatic injection of dynamic configuration.

Data loading process of Nacos client

The final loading of configuration data is based on configservice Getconfig is implemented by the SDK provided by Nacos.

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

Tutorial on using the Nacos SDK: https://nacos.io/zh-cn/docs/s...

In other words, next, our source code analysis directly enters 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); //Get group. If it is empty, it is default group
    ParamUtils.checkKeyParam(dataId, group);   //Validation request parameters
    ConfigResponse cr = new ConfigResponse(); //Set response results
    
    cr.setDataId(dataId); 
    cr.setTenant(tenant);
    cr.setGroup(group);
    
    // Local configuration is preferred
    String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
    if (content != null) { //If the content in the local cache is not empty
        
        LOGGER.warn("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}, config={}", agent.getName(),
                dataId, group, tenant, ContentUtils.truncateContent(content));
        cr.setContent(content); //Set the content to cr.
        //Get the encryptedDataKey of disaster recovery configuration
        String encryptedDataKey = LocalEncryptedDataKeyProcessor
                .getEncryptDataKeyFailover(agent.getName(), dataId, group, tenant);
        cr.setEncryptedDataKey(encryptedDataKey); //Save to cr
        configFilterChainManager.doFilter(null, cr); //Execute filtering (it doesn't seem to be implemented at present)
        content = cr.getContent(); //Return file content
        return content;
    }
    //If there is no relevant content in the local file, a remote call is initiated
    try {
        ConfigResponse response = worker.getServerConfig(dataId, group, tenant, timeoutMs);
        //Return the response content
        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());
    }
    //If there is a NacosException and it is not 403 exception, try to get the configuration through the local snapshot file and return.
    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

If the local cached content exists, the content data is returned; otherwise, a null value is returned.

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 working 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)) { //If group is empty, the default group is returned
        group = Constants.DEFAULT_GROUP;
    }
    
    HttpRestResult<String> result = null;
    try {
        Map<String, String> params = new HashMap<String, String>(3);  //Build request parameters
        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);
        }
        //Initiate remote call
        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);
    }
    //Different processing is realized according to the response results
    switch (result.getCode()) { 
        case HttpURLConnection.HTTP_OK: //If the response is successful, save the snapshot locally and return the response content
            LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, result.getData());
            configResponse.setContent(result.getData());
            String configType;  //Type of configuration file, such as text, json, yaml, etc
            if (result.getHeader().getValue(CONFIG_TYPE) != null) {
                configType = result.getHeader().getValue(CONFIG_TYPE);
            } else {
                configType = ConfigType.TEXT.getType();
            }
            configResponse.setConfigType(configType); //Set it to configResponse, and then implement different parsing strategies according to the file type
            //Get the key of encrypted data
            String encryptedDataKey = result.getHeader().getValue(ENCRYPTED_DATA_KEY);
            //preservation
            LocalEncryptedDataKeyProcessor
                    .saveEncryptDataKeySnapshot(agent.getName(), dataId, group, tenant, encryptedDataKey);
            configResponse.setEncryptedDataKey(encryptedDataKey);
            return configResponse;
        case HttpURLConnection.HTTP_NOT_FOUND: //If 404 is returned, empty the local snapshot
            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

Implementation of 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);  //Inject security information
    String currentServerAddr = serverListMgr.getCurrentServerAddr();//Get the current server address
    int maxRetry = this.maxRetry; //Gets the maximum number of retries. The default is 3
    //Configure the properties of HttpClient. The default readTimeOut timeout is 3s
    HttpClientConfig httpConfig = HttpClientConfig.builder()
            .setReadTimeOutMillis(Long.valueOf(readTimeoutMs).intValue())
            .setConTimeOutMillis(ConfigHttpClientManager.getInstance().getConnectTimeoutOrDefault(100)).build();
    do {
       
        try {
            //Set header
            Header newHeaders = getSpasHeaders(paramValues, encode);
            if (headers != null) {
                newHeaders.addAll(headers);
            }
            //Building query criteria
            Query query = Query.newInstance().initParams(paramValues);
            //Initiate an HTTP request, 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)) { //If the request fails,
                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 there are multiple servers in the list and the current request fails, try to retry with the next address
        if (serverListMgr.getIterator().hasNext()) {
            currentServerAddr = serverListMgr.getIterator().next();
        } else {
            maxRetry--; //The number of retries decreases
            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");
}

Configuration acquisition of Nacos Server

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

Locate the configcontroller in the Nacos source code The code of the method in getconfig 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); //Tenant, i.e. namespaceid
    // check params
    ParamUtils.checkParam(dataId, group, "datumId", "content"); //Check whether the request parameter is empty
    ParamUtils.checkParam(tag);
    
    final String clientIp = RequestUtil.getRemoteIp(request); //Get the requested ip
    inner.doGetConfig(request, response, dataId, group, tenant, tag, clientIp); //load configuration
}

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); //Application name of the requester
   
    int lockResult = tryConfigReadLock(groupKey);  //Try to obtain the read lock configured by the current request (to avoid read-write conflict)
    
    final String requestIp = RequestUtil.getRemoteIp(request); //ip address of the requester
    
    boolean isBeta = false;
    //Lockresult > 0 indicates that the cacheitem (that is, the configuration item of the cache) is not empty and has been locked for reading, which means that the cached data cannot be deleted.
    //lockResult=0 indicates that the cacheItem is empty and no read lock is required
    //lockResult=01 indicates that locking failed and there is a conflict.
    //The following if is to deal with these three cases.
    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;
            //Get the CacheItem from the local cache according to the groupKey
            CacheItem cacheItem = ConfigCacheService.getContentCache(groupKey);
            //Determine whether it is a beta release, that is, a test version
            if (cacheItem.isBeta() && cacheItem.getIps4Beta().contains(clientIp)) {
                isBeta = true;
            }
            //Gets the type of configuration file
            final String configType =
                    (null != cacheItem.getType()) ? cacheItem.getType() : FileTypeEnum.TEXT.getFileType();
            response.setHeader("Config-Type", configType);
            //Returns an enumerated object of a file type
            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) { //In case of test configuration
                md5 = cacheItem.getMd54Beta();
                lastModified = cacheItem.getLastModifiedTs4Beta();
                if (PropertyUtil.isDirectRead()) {
                    configInfoBase = persistService.findConfigInfo4Beta(dataId, group, tenant);
                } else {
                    file = DiskUtil.targetBetaFile(dataId, group, tenant); //Get the File from the disk and get a complete File
                }
                response.setHeader("isBeta", "true");
            } else {
                if (StringUtils.isBlank(tag)) { //Judge whether the tag tag is empty. The tag corresponds to the tag option in the nacos configuration center
                    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 {//Directly follow this logic (the tag attribute will not be configured by default)
                        md5 = cacheItem.getMd5(); //Get cached md5
                        lastModified = cacheItem.getLastModifiedTs(); //Get last update time
                        if (PropertyUtil.isDirectRead()) {  //Judge whether it is a standalone mode and uses the derby database. If so, load data from the derby database
                            configInfoBase = persistService.findConfigInfo(dataId, group, tenant);
                        } else {
                            //Otherwise, in case of database or cluster mode, get the file from the local disk first
                            file = DiskUtil.targetFile(dataId, group, tenant);
                        }
                        //If the local disk file is empty and configinfbase is empty, the configuration data does not exist and null is returned directly
                        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 {//If the tag is not empty, it indicates that the tag tag is set in the configuration file
                    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 + "";
                    }
                }
            }
            //Set the obtained data result to the response and return
            
            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 it is in stand-alone mode, write the data directly back to the client
            if (PropertyUtil.isDirectRead()) {
                out = response.getWriter();
                out.print(configInfoBase.getContent());
                out.flush();
                out.close();
            } else {//Otherwise, through 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); //Release lock
            IoUtils.closeQuietly(fis);
        }
    } else if (lockResult == 0) { //Description cache is empty,
        
        // 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

Getting data content from derby database 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 directly find the file in the specified directory 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 configuration acquisition from Nacos Server and cached locally, so as to realize the ability of Nacos dynamic configuration acquisition!

Copyright notice: unless otherwise stated, all articles on this blog adopt CC BY-NC-SA 4.0 license agreement. Reprint please indicate from Mic to take you to learn architecture!
If this article is helpful to you, please pay attention and praise. Your persistence is the driving force of my continuous creation. Welcome to WeChat public official account for more dry cargo.

Keywords: Java

Added by inspire on Tue, 01 Mar 2022 02:24:53 +0200