Nacos客戶端本地緩存和故障轉(zhuǎn)移方式
在Nacos客戶端從Server獲得服務(wù)的時候,在某些時候出現(xiàn)了一些故障, 這時候為了保證服務(wù)正常,Nacos進行了故障轉(zhuǎn)移,原理就是將之前緩存的服務(wù)信息拿出來用,防止服務(wù)出現(xiàn)問題,涉及到的核心類為ServiceInfoHolder和FailoverReactor。
本地緩存有兩方面,第一方面是從注冊中心獲得實例信息會緩存在內(nèi)存當中,也就是通過Map的形式承載,這樣查詢操作都方便。第二方面便是通過磁盤文件的形式定時緩存起來,以備不時之需。
故障轉(zhuǎn)移也分兩方面,第一方面是故障轉(zhuǎn)移的開關(guān)是通過文件來標記的;第二方面是當開啟故障轉(zhuǎn)移之后,當發(fā)生故障時,可以從故障轉(zhuǎn)移備份的文件中來獲得服務(wù)實例信息。
1. ServiceInfoHolder
ServiceInfoHolder類,顧名思義,服務(wù)信息的持有者。每次客戶端從注冊中心獲取新的服務(wù)信息時都會調(diào)用該類,其中processServiceInfo方法來進行本地化處理,包括更新緩存服務(wù)、發(fā)布事件、更新本地文件等。
ServiceInfoHolder類持有了ServiceInfo,通過一個ConcurrentMap來儲存
// ServiceInfoHolder private final ConcurrentMap<String, ServiceInfo> serviceInfoMap;
當從服務(wù)端拉會服務(wù)信息時,就會往這個map中進行存儲。
// ServiceInfoHolder
public ServiceInfo processServiceInfo(ServiceInfo serviceInfo) {
....
//緩存服務(wù)信息
serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
....
}在創(chuàng)建ServiceInfoHolder時會做如下事情
- 初始化本地緩存目錄
- 根據(jù)配置從本地緩存初始化服務(wù),默認false
- 創(chuàng)建FailoverReactor并相互持有ServiceInfoHolder
// ServiceInfoHolder
public ServiceInfoHolder(String namespace, Properties properties) {
initCacheDir(namespace, properties);
if (isLoadCacheAtStart(properties)) {
this.serviceInfoMap = new ConcurrentHashMap<>(DiskCache.read(this.cacheDir));
} else {
this.serviceInfoMap = new ConcurrentHashMap<>(16);
}
this.failoverReactor = new FailoverReactor(this, cacheDir);
this.pushEmptyProtection = isPushEmptyProtect(properties);
}
本地緩存目錄
在processServiceInfo中會進行本地緩存寫入,其實就是寫入這個目錄(這個目錄是在創(chuàng)建ServiceInfoHolder時根據(jù)配置初始化的),所以目錄中的數(shù)據(jù)正常是最新讀取到的服務(wù)信息
// ServiceInfoHolder
public ServiceInfo processServiceInfo(ServiceInfo serviceInfo) {
....
// 記錄Service本地文件
DiskCache.write(serviceInfo, cacheDir);
....
}本地緩存目錄默認路徑:${user.home}/nacos/naming/public,也可以自定義,通過System.setProperty("JM.SNAPSHOT.PATH")自定義
2. FailoverReactor
在ServiceInfoHolder的構(gòu)造方法中,還會初始化一個FailoverReactor類,同樣是ServiceInfoHolder的成員變量。FailoverReactor的作用便是用來處理故障轉(zhuǎn)移的。
構(gòu)造故障轉(zhuǎn)移做了如下事情:
// FailoverReactor
public FailoverReactor(ServiceInfoHolder serviceInfoHolder, String cacheDir) {
// 持有ServiceInfoHolder的引用
this.serviceInfoHolder = serviceInfoHolder;
// 拼接故障目錄:${user.home}/nacos/naming/public/failover
this.failoverDir = cacheDir + FAILOVER_DIR;
// 初始化executorService,支持延時執(zhí)行
this.executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
// 守護線程模式運行
thread.setDaemon(true);
thread.setName("com.alibaba.nacos.naming.failover");
return thread;
}
});
// 其他初始化操作,通過executorService開啟多個定時任務(wù)執(zhí)行
this.init();
}init方法執(zhí)行
在這個方法中開啟了三個定時任務(wù),這三個任務(wù)其實都是FailoverReactor的內(nèi)部類
1. 初始化立即執(zhí)行,然后每間隔5秒再執(zhí)行SwitchRefresher
2. 初始化延遲30分鐘執(zhí)行,執(zhí)行間隔24小時,執(zhí)行任務(wù)DiskFileWriter
3. 初始化立即執(zhí)行,執(zhí)行間隔10秒,執(zhí)行核心操作為DiskFileWriter
// FailoverReactor
public void init() {
// 初始化立即執(zhí)行,執(zhí)行間隔5秒,執(zhí)行任務(wù)SwitchRefresher
executorService.scheduleWithFixedDelay(new SwitchRefresher(), 0L, 5000L, TimeUnit.MILLISECONDS);
// 初始化延遲30分鐘執(zhí)行,執(zhí)行間隔24小時,執(zhí)行任務(wù)DiskFileWriter
executorService.scheduleWithFixedDelay(new DiskFileWriter(), 30, DAY_PERIOD_MINUTES, TimeUnit.MINUTES);
// 10秒后如果故障目錄為空,則執(zhí)行DiskFileWriter任務(wù)強制備份
executorService.schedule(new Runnable() {
@Override
public void run() {
try {
File cacheDir = new File(failoverDir);
...
File[] files = cacheDir.listFiles();
if (files == null || files.length <= 0) {
new DiskFileWriter().run();
}
} catch (Throwable e) {
NAMING_LOGGER.error("[NA] failed to backup file on startup.", e);
}
}
}, 10000L, TimeUnit.MILLISECONDS);
}DiskFileWriter刷盤任務(wù)
將ServiceInfo寫入備份磁盤
// FailoverReactor
class DiskFileWriter extends TimerTask {
@Override
public void run() {
Map<String, ServiceInfo> map = serviceInfoHolder.getServiceInfoMap();
for (Map.Entry<String, ServiceInfo> entry : map.entrySet()) {
ServiceInfo serviceInfo = entry.getValue();
...
// 將緩存寫入磁盤
DiskCache.write(serviceInfo, failoverDir);
}
}
}SwitchRefresher根據(jù)標記故障轉(zhuǎn)移文件切換內(nèi)存標志
- 如果故障轉(zhuǎn)移文件不存在,則直接返回(文件開關(guān))
- 比較文件修改時間,如果已經(jīng)修改,則獲取故障轉(zhuǎn)移文件中的內(nèi)容。
- 故障轉(zhuǎn)移文件中存儲了0和1標識。0表示關(guān)閉,1表示開啟。
- 當為開啟狀態(tài)時,執(zhí)行線程FailoverFileReader。
// FailoverReactor
class SwitchRefresher implements Runnable {
long lastModifiedMillis = 0L;
@Override
public void run() {
try {
File switchFile = new File(failoverDir + UtilAndComs.FAILOVER_SWITCH);
// 文件不存在則退出
if (!switchFile.exists()) {
...
return;
}
long modified = switchFile.lastModified();
if (lastModifiedMillis < modified) {
lastModifiedMillis = modified;
// 獲取故障轉(zhuǎn)移文件內(nèi)容
String failover = ConcurrentDiskUtil.getFileContent(failoverDir + UtilAndComs.FAILOVER_SWITCH,
Charset.defaultCharset().toString());
if (!StringUtils.isEmpty(failover)) {
String[] lines = failover.split(DiskCache.getLineSeparator());
for (String line : lines) {
String line1 = line.trim();
// 1 表示開啟故障轉(zhuǎn)移模式
if (IS_FAILOVER_MODE.equals(line1)) {
switchParams.put(FAILOVER_MODE_PARAM, Boolean.TRUE.toString());
new FailoverFileReader().run();
// 0 表示關(guān)閉故障轉(zhuǎn)移模式
} else if (NO_FAILOVER_MODE.equals(line1)) {
switchParams.put(FAILOVER_MODE_PARAM, Boolean.FALSE.toString());
}
}
} else {
switchParams.put(FAILOVER_MODE_PARAM, Boolean.FALSE.toString());
}
}
} catch (Throwable e) {
NAMING_LOGGER.error("[NA] failed to read failover switch.", e);
}
}
}FailoverFileReader故障后讀取備份文件
該任務(wù)是故障轉(zhuǎn)移的核心, 故障轉(zhuǎn)移文件讀取,基本操作就是讀取failover目錄存儲的**備份服務(wù)信息文件**內(nèi)容,然后轉(zhuǎn)換成ServiceInfo,并且將所有的ServiceInfo儲存在FailoverReactor的ServiceMap屬性中。
流程如下:
1. 讀取failover目錄下的所有文件,進行遍歷處理
2. 如果文件不存在跳過
3. 如果文件是故障轉(zhuǎn)移開關(guān)標志文件跳過
4. 讀取文件中的備份內(nèi)容,轉(zhuǎn)換為ServiceInfo對象
5. 將ServiceInfo對象放入到domMap中
6. 最后判斷domMap不為空,賦值給serviceMap
// FailoverReactor
class FailoverFileReader implements Runnable {
@Override
public void run() {
Map<String, ServiceInfo> domMap = new HashMap<String, ServiceInfo>(16);
File cacheDir = new File(failoverDir);
File[] files = cacheDir.listFiles();
if (files == null) {
return;
}
for (File file : files) {
// 如果是故障轉(zhuǎn)移標志文件,則跳過
if (file.getName().equals(UtilAndComs.FAILOVER_SWITCH)) {
continue;
}
ServiceInfo dom = new ServiceInfo(file.getName());
BufferedReader reader = null;
try {
String dataString = ConcurrentDiskUtil
.getFileContent(file, Charset.defaultCharset().toString());
reader = new BufferedReader(new StringReader(dataString));
String json = reader.readLine();
dom = JacksonUtils.toObj(json, ServiceInfo.class);
} finally {
reader.close();
}
if (!CollectionUtils.isEmpty(dom.getHosts())) {
domMap.put(dom.getKey(), dom);
}
}
// 讀入緩存
if (domMap.size() > 0) {
serviceMap = domMap;
}
}
}·FailoverReactor.serviceMap的使用
我們在ServiceInfoHolder中的getServiceInfo方法就會判斷,如果當前是故障切換狀態(tài),就會從FailoverReactor中獲取服務(wù),那么這是就用到了FailoverReactor.serviceMap緩存的服務(wù)了
// ServiceInfoHolder
public ServiceInfo getServiceInfo(final String serviceName, final String groupName, final String clusters) {
...
if (failoverReactor.isFailoverSwitch()) {
return failoverReactor.getService(key);
}
return serviceInfoMap.get(key);
}// FailoverReactor
public ServiceInfo getService(String key) {
ServiceInfo serviceInfo = serviceMap.get(key);
if (serviceInfo == null) {
serviceInfo = new ServiceInfo();
serviceInfo.setName(key);
}
return serviceInfo;
}總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java List簡介_動力節(jié)點Java學(xué)院整理
Java中可變數(shù)組的原理就是不斷的創(chuàng)建新的數(shù)組,將原數(shù)組加到新的數(shù)組中,下文對Java List用法做了詳解。需要的朋友參考下吧2017-05-05
Java靜態(tài)內(nèi)部類實現(xiàn)單例過程
這篇文章主要介紹了Java靜態(tài)內(nèi)部類實現(xiàn)單例過程,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-10-10
mybatis初始化SqlSessionFactory失敗的幾個原因分析
這篇文章主要介紹了mybatis初始化SqlSessionFactory失敗的幾個原因分析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
Java使用easyExcel批量導(dǎo)入數(shù)據(jù)詳解
這篇文章主要介紹了Java使用easyExcel批量導(dǎo)入數(shù)據(jù)詳解,通常我們會提供一個模板,此模塊我們可以使用easyExcel導(dǎo)出數(shù)據(jù)生成的一個Excel文件當作模板,提供下載鏈接,用戶在該文件內(nèi)填入規(guī)定的數(shù)據(jù)格式以后可以批量導(dǎo)入數(shù)據(jù)到數(shù)據(jù)庫中,需要的朋友可以參考下2023-08-08
一步步教你整合SSM框架(Spring MVC+Spring+MyBatis)詳細教程
使用SSM(Spring、SpringMVC和Mybatis)已經(jīng)有段時間了,項目在技術(shù)上已經(jīng)沒有什么難點了,基于現(xiàn)有的技術(shù)就可以實現(xiàn)想要的功能,下面這篇文章主要給大家介紹了關(guān)于整合SSM框架:Spring MVC + Spring + MyBatis的相關(guān)資料,需要的朋友可以參考下。2017-07-07

