ConfigService 是提供給開發者使用的,用來對配置文件進行相關操作的核心接口,比如獲取/監聽/發佈/刪除配置項等,這一節我們來分析下獲取配置內容的流程,對應的是 ConfigService#getConfig() 方法。

String getConfig(String dataId, String group, long timeoutMs) 方法有幾個參數:

  • dataId :配置文件名;
  • group :配置文件所屬 group
  • timeoutMs
    nacos server
    timeoutMs
    

這裏沒有指定 namespace ,是因爲 namespace 是和 ConfigService 綁定的,即每個 ConfigService 只能對應一個 namespacenamespace 設計就是用來進行隔離。

準備工作

//group空則賦值DEFAULT_GROUP
group = null2defaultGroup(group);
//校驗dataId、group是否合法,不爲空且必須是數字、字母或[- _ . :]四個字符,其它都是非法
ParamUtils.checkKeyParam(dataId, group);
//返回結果封裝
ConfigResponse cr = new ConfigResponse();

cr.setDataId(dataId);
cr.setTenant(tenant);
cr.setGroup(group);

主要是對參數進行校驗,以及創建一個 ConfigResponse 用來封裝返回結果信息。

從FailoverFile獲取配置

在向 Nacos Server 發送 http 請求獲取配置內容之前,會調用 LocalConfigInfoProcessor.getFailover 先嚐試從本地配置文件 FailoverFile 獲取,代碼如下:

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);
//調用ConfigFilter.doFilter()方法
configFilterChainManager.doFilter(null, cr);
content = cr.getContent();
//如果本地配置存在,則直接返回本地配置,而不用去nacos server上去拉取
return content;
}

FailoverFile 在客戶端不會自動生成,正常情況下 Failover 文件都是不存在的,設計這種機制可能主要是某種場景下擴展,比如用戶系統自行維護配置變更,將最新的配置內容寫入到本地 Failover 文件中,這樣就不需要向 Nacos Server 發送 http 請求獲取配置。

Failover 文件路徑: 系統用戶根目錄+nacos/config+agentName+data+config-data+[group名稱]+[dataId名稱] ,比如: C:\Users\Administrator\nacos\config\fixed-127.0.0.1_8848-192.168.1.1_9999_nacos\data\config-data\DEFAULT_GROUP\other

從Nacos Server獲取配置

如果 Failover 方式沒有獲取到配置信息,則向 Nacos Server 獲取配置內容,核心代碼如下:

try {
//去nacos server上拉取
String[] ct = worker.getServerConfig(dataId, group, tenant, timeoutMs);
cr.setContent(ct[0]);
//調用ConfigFilter chain對獲取的配置進行處理
configFilterChainManager.doFilter(null, cr);
content = cr.getContent();

return content;
} catch (NacosException ioe) {
if (NacosException.NO_RIGHT == ioe.getErrCode()) {//403沒有權限,拒絕訪問
throw ioe;
}
LOGGER.warn("[{}] [get-config] get from server error, dataId={}, group={}, tenant={}, msg={}", agent.getName(), dataId, group, tenant, ioe.toString());
}

關鍵代碼是: worker.getServerConfig(dataId, group, tenant, timeoutMs) ,即委託給 ClientWorker 對象:

public String[] getServerConfig(String dataId, String group, String tenant, long readTimeout)
throws NacosException {
String[] ct = new String[2];
if (StringUtils.isBlank(group)) {
group = Constants.DEFAULT_GROUP;
}

HttpResult result = null;
try {
List<String> params = null;
if (StringUtils.isBlank(tenant)) {
params = new ArrayList<String>(Arrays.asList("dataId", dataId, "group", group));
} else {
params = new ArrayList<String>(Arrays.asList("dataId", dataId, "group", group, "tenant", tenant));
}
//利用ServerHttpAgent向nacos server發送get請求,path=/v1/cs/configs,params=dataId+group+tenant(namespace)
result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout);
} catch (IOException e) {
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, e);
throw new NacosException(NacosException.SERVER_ERROR, e);
}

switch (result.code) {
case HttpURLConnection.HTTP_OK:
//更新本地對應的snapshot配置文件
LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, result.content);
ct[0] = result.content;
//設置配置文件類型,如:properties、yaml等
if (result.headers.containsKey(CONFIG_TYPE)) {
ct[1] = result.headers.get(CONFIG_TYPE).get(0);
} else {
ct[1] = ConfigType.TEXT.getType();
}
return ct;
case HttpURLConnection.HTTP_NOT_FOUND:
LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, null);
return ct;
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.code, result.content);
}
default: {
LOGGER.error("[{}] [sub-server-error] dataId={}, group={}, tenant={}, code={}", agent.getName(), dataId, group, tenant, result.code);
throw new NacosException(result.code,
"http error, code=" + result.code + ",dataId=" + dataId + ",group=" + group + ",tenant=" + tenant);
}
}
}

代碼看着比較多,但是邏輯還是比較簡單,大致流程:

  • ServerHttpAgent
    Nacos
    http
    path=/v1/cs/configs,params=dataId+group+tenant(namespace)
    ServerHttpAgent
    Nacos
    http
    params
    
  • switch (result.code)
    http
    200
    saveSnapshot()
    SnapshotFile
    Nacos Server
    
  • 然後就是請求異常處理,這裏主要分爲兩類:

    • HTTP_NOT_FOUND 即404異常,會將本地對應的SnapshotFile文件內容清空,然後返回 null
    • 其它異常都會拋出異常;

從SnapshotFile獲取配置

如果從 Nacos Server 獲取配置出現異常,即 ClientWorker.getServerConfig() 方法拋出異常,則會從本地 SnapshotFile 中獲取配置內容,核心代碼如下:

LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}", agent.getName(),
dataId, group, tenant, ContentUtils.truncateContent(content));
//如果從nacos server上獲取配置失敗,則從本地snapshot中獲取配置內容
content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);
cr.setContent(content);
//調用ConfigFilter chain對獲取的配置進行處理
configFilterChainManager.doFilter(null, cr);
content = cr.getContent();
return content;

總結

調用 ConfigService.getConfig() 獲取配置的流程分析完成,從上面分析流程來看,獲取配置大致可以總結如下:

  1. FailoverFile
    Nacos Server
    http
    FailoverFile
    FailoverFile
    
  2. FailoverFile
    ServerHttpAgent
    Nacos
    http
    path=/v1/cs/configs,params=dataId+group+tenant(namespace)
    SnapshotFile
    
  3. ServerHttpAgent
    SnapshotFile
    null
    SnapshotFile
    Nacos Server
    

長按識別關注,持續輸出原創

相關文章