关于java:Spring-Cloud-Nacos实现动态配置加载的源码分析

9次阅读

共计 21164 个字符,预计需要花费 53 分钟才能阅读完成。

了解了上述 Environment 的基本原理后,如何从近程服务器上加载配置到 Spring 的 Environment 中。

NacosPropertySourceLocator

顺着后面的剖析思路,咱们很天然的去找 PropertySourceLocator 的实现类,发现除了咱们自定义的 GpJsonPropertySourceLocator 以外,还有另外一个实现类NacosPropertySourceLocator.

于是,间接来看 NacosPropertySourceLocator 中的 locate 办法,代码如下。

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

上述代码的实现不难理解

  1. 获取 nacos 客户端的配置属性,并生成 dataId(这个很重要,要定位 nacos 的配置)
  2. 别离调用三个办法从加载配置属性源,保留到 composite 组合属性源中

loadApplicationConfiguration

咱们能够先不论加载共享配置、扩大配置的办法,最终实质上都是去近程服务上读取配置,只是传入的参数不一样。

  • fileExtension,示意配置文件的扩展名
  • nacosGroup 示意分组
  • 加载 dataid= 项目名称 的配置
  • 加载 dataid= 项目名称 + 扩展名 的配置
  • 遍历以后配置的激活点(profile),别离循环加载带有 profile 的 dataid 配置
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

调用 loadNacosPropertySource 加载存在的配置信息。

把加载之后的配置属性保留到 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

这个办法,就是连贯近程服务器去获取配置数据的实现,要害代码是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();}

阶段性总结

通过上述剖析,咱们晓得了 Spring Cloud 集成 Nacos 时的要害门路,并且晓得在启动时,Spring Cloud 会从 Nacos Server 中加载动态数据保留到 Environment 汇合。

从而实现动静配置的主动注入。

Nacos 客户端的数据的加载流程

配置数据的最终加载,是基于 configService.getConfig,Nacos 提供的 SDK 来实现的。

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

对于 Nacos SDK 的应用教程:https://nacos.io/zh-cn/docs/s…

也就是说,接下来咱们的源码剖析,间接进入到 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;
}

从本地缓存读取配置

默认状况下,nacos 先从本地缓存的配置中读取文件:C:\Users\mayn\nacos\config\fixed-192.168.8.133_8848-6a382560-ed4c-414c-a5e2-9d72c48f1a0e_nacos

如果本地缓存内容存在,则返回内容数据,否则返回空值。

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

ClientWorker.getServerConfig

ClientWorker,示意客户端的一个工作类,它负责和服务端交互。

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

发动近程申请的实现。

@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 端的配置获取

客户端向服务端加载配置,调用的接口是:/nacos/v1/cs/configs,于是,在 Nacos 的源码中找到该接口

定位到 Nacos 源码中的 ConfigController.getConfig 中的办法,代码如下:

@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

从 derby 数据库中获取数据内容, 这个就是一个根本的数据查问操作。

@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

从磁盘目录中获取指标文件,间接依据 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;
}

至此,NacosPropertySourceLocator 实现了从 Nacos Server 上动静获取配置并缓存到本地,从而实现 Nacos 动静配置获取的能力!

版权申明:本博客所有文章除特地申明外,均采纳 CC BY-NC-SA 4.0 许可协定。转载请注明来自 Mic 带你学架构
如果本篇文章对您有帮忙,还请帮忙点个关注和赞,您的保持是我一直创作的能源。欢送关注同名微信公众号获取更多技术干货!

正文完
 0