主頁 > 後端開發 > Spring Cloud Nacos實作動態配置加載的原始碼分析

Spring Cloud Nacos實作動態配置加載的原始碼分析

2022-03-02 06:13:43 後端開發

理解了上述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 = https://www.cnblogs.com/mic112/p/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/sdk.html

也就是說,接下來我們的原始碼分析,直接進入到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 = "https://www.cnblogs.com/mic112/p/tenant", required = false, defaultValue = https://www.cnblogs.com/mic112/p/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帶你學架構
如果本篇文章對您有幫助,還請幫忙點個關注和贊,您的堅持是我不斷創作的動力,歡迎關注「跟著Mic學架構」公眾號公眾號獲取更多技術干貨!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/435327.html

標籤:Java

上一篇:阿里云視頻點播

下一篇:SpringBoot+MybatisPlus+Mysql+Sharding-JDBC分庫分表實踐

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more