目前Nacos客戶端有一個FailoverReactor來進行容災文件的管理,可以通過在指定磁盤文件里寫入容災數據來進行客戶端使用數據的覆蓋。FailoverReactor目前會攔截Nacos客戶端查詢接口調用,以getAllInstances接口為例,目前FailoverReactor的工作流程如下圖:
這里主要涉及到兩個組件:
- FailoverReactor:容災數據管理類,負責容災開關和數據的加載和刷新;
- ServiceInfoHoler:默認的服務數據管理類,持有一份內存服務數據緩存,負責處理Nacos服務端的推送的最新數據;
FailoverReactor和ServiceInfoHoler的交互機制如下:
這里和客戶端容災的相關邏輯主要是3個:
- FailoverReactor會定期讀取磁盤容災開關文件;
- 當容災開關打開時,FailoverReactor會從磁盤里加載容災數據文件,同時用戶調用查詢請求時就會優先使用容災文件里的數據;
- FailoverReactor會定期從ServiceInfoHolder拿到最新的內存數據,保存到容災磁盤數據文件里;
目前的方式有四個問題:
- 無法人工覆蓋容災數據,當前是定期將ServiceInfoHolder里的數據進行持久化作為容災數據。即使手動修改了容災數據磁盤文件內容,也會被覆蓋為ServiceInfoHolder里的數據;
- 基于磁盤的容災緩存有一些限制,比如服務的訂閱者有多個機器實例時,如果需要打開容災開關,需要運維批量修改機器的文件;
- subscribe接口不會使用FailoverReactor里的數據;
- 容災數據的可觀測性也需要優化。
實現方案
上面提到的問題1、3和4都比較好解決,下面會一一闡述。針對問題2,一般在生產環境中,我們會考慮使用中心化的數據存儲來進行容災數據的存儲和管理。我們可以將FailoverReactor依賴的數據 來源抽象為一個SPI接口FailoverDataSource,這個接口默認實現還是本地磁盤,但是用戶可以實現這個SPI接口來使用自定義的容災數據源。
接口和數據結構定義
FailoverDataSource的定義為:
public interface FailoverDataSource {FailoverSwitch getSwitch();Map<String, FailoverData> getFailoverData();
}
各個方法的作用為:
- FailoverSwitch getSwitch():獲取當前的容災開關;
- Map<String, FailoverData> getFailoverData():獲取當前的容災數據,返回一個map,key是服務名,value為對應服務的容災數據;
FailoverSwitch的定義建議如下:
public class FailoverSwitch {private boolean enabled;
}
- enabled:容災是否打開;
FailoverData的定義建議為:
public abstract class FailoverData {private DataType dataType;private Object data;protected FailoverData(Object data, DataType dataType) {this.data = data;this.dataType = dataType;}enum DataType {naming,config}
}
- dataType:容災數據類型,這里因為需要綜合考慮配置模塊的容災,所以使用抽象類定義了naming和config兩種容災數據類型;
- data:容災數據,子類設置具體類型;
以naming模塊為例,NamingFailoverData擴展FailoverData:
public class NamingFailoverData extends FailoverData {private NamingFailoverData(ServiceInfo serviceInfo) {super(serviceInfo, DataType.naming);}public static NamingFailoverData newNamingFailoverData(ServiceInfo serviceInfo) {return new NamingFailoverData(serviceInfo);}
}
NamingFailoverData里的容災數據類型為ServiceInfo。
交互流程
FailoverReactor內部流程
FailoverReactor和FailoverDataSource、ServiceInfoHoler的交互機制優化為:
各個組件職責說明如下:
- FailoverReactor:存儲容災緩存數據,管理容災開關定時任務和容災數據更新定時任務;
- FailoverSwitchRefresher:定時(每5秒)從容災數據源查詢容災開關,根據容災開關的值進行相應操作:
- 若容災打開:從容災數據源查詢容災數據FailoverDataSource,保存到FailoverReactor的內存容災數據map里;
- 若容災關閉:清空FailoverReactor的內存容災數據map;
- FailoverDataSource:存儲容災數據,前文已經提及;
- ServiceInfoHoler:默認情況下服務數據的管理,前文已經提及;
客戶端查詢請求流程
對于客戶端的查詢請求,其流程優化為:
這里的流程和之前的變化為:當從failoverReactor里拿不到容災數據的時候,還是會去serviceInfoHolder里讀取數據。這么做的目的是因為我們可能只配置部分服務進行容災,其他的服務還是走serviceInfoHolder。
訂閱接口事件通知流程
對于訂閱接口,之前是不會受到容災開關的影響,現在則也會在容災開啟時停止數據更新通知:
public ServiceInfo processServiceInfo(ServiceInfo serviceInfo) {...if (changed) {NAMING_LOGGER.info("current ips:({}) service: {} -> {}", serviceInfo.ipCount(), serviceInfo.getKey(),JacksonUtils.toJson(serviceInfo.getHosts()));// 判斷容災開關是否打開,打開時不發布事件:if (!failoverDataSource.getFailoverSwitch().isEnabled()) {NotifyCenter.publishEvent(new InstancesChangeEvent(notifierEventScope, serviceInfo.getName(), serviceInfo.getGroupName(),serviceInfo.getClusters(), serviceInfo.getHosts()));}DiskCache.write(serviceInfo, cacheDir);}return serviceInfo;}
同時,在容災關閉時,我們需要根據容災期間數據是否發生變化來決定要不要觸發訂閱事件通知,我們可以把這個邏輯駕到上面提到的FailoverSwitchRefresher里:
在這里當FailoverSwitchRefresher輪詢發現容災關閉時,在清空FailoverReactor的內存數據之前,會觸發FailoverReactor和ServiceInfoHolder的數據比較,如果發現數據不一致,則會觸發ServiceInfoHolder發布對應的服務變更事件。
可觀測性
我們可以定義個MultiGauge來存儲FailoverReactor里目前生效的容災數據內容,統計粒度為每個服務當前有多少個實例:
MultiGauge failoverInstanceCounts = MultiGauge.builder("nacos_naming_client_failover_instances").register(Metrics.globalRegistry);Set<String> serviceNames = failoverDataSource.getSwitch().getServiceNames();
Map<String, FailoverData> failoverDataMap = failoverDataSource.getFailoverData();failoverInstanceCounts.register(serviceNames.stream().map(serviceName -> MultiGauge.Row.of(Tags.of("service_name", serviceName), ((ServiceInfo)failoverDataMap.get(serviceName)).ipCount())).collect(Collectors.toList()), true);
測試用例
以下場景需要進行測試:
- 打開容災開關后,對于包含在容災的服務列表里的服務,Nacos服務端數據變化不影響客戶端查詢和訂閱;
- 關閉容災開關后,對于所有服務客戶端查詢到最新數據;
- 關閉容災開關后,若訂閱的服務數據在容災期間有變化,會觸發一次訂閱通知;
- 設置容災的服務列表,不在服務列表里的不會使用容災數據;
- 使用自定義的容災實現,可以被加載并運行;
原文出自:Nacos官網?