When the Nacos client obtains services from the Server, some failures occur at some point. In order to ensure the normal service, Nacos fails over. The principle is to take out the previously cached service information to prevent problems with the service. The core classes involved are ServiceInfoHolder and FailoverReactor.
There are two aspects of local cache. The first aspect is that the instance information obtained from the registration center will be cached in memory, that is, it is carried through the form of a map, so that query operations are convenient. The second aspect is to cache it regularly in the form of disk files in case of emergencies.
Failover is also divided into two aspects. The first aspect is that the failover switch is marked by a file; the second aspect is that when failover is turned on, when a failure occurs, service instance information can be obtained from the failover backup file.
1. ServiceInfoHolder
ServiceInfoHolder class, as the name implies, is the holder of service information. This class will be called every time the client obtains new service information from the registry center, and the processServiceInfo method is used to perform localization processing, including updating the cache service, publishing events, updating local files, etc.
The ServiceInfoHolder class holds ServiceInfo and stores it through a ConcurrentMap.
// ServiceInfoHolder private final ConcurrentMap<String, ServiceInfo> serviceInfoMap;
When the service information is pulled from the server, it will be stored in this map.
// ServiceInfoHolder public ServiceInfo processServiceInfo(ServiceInfo serviceInfo) { .... //Cache service information ((), serviceInfo); .... }
When creating a ServiceInfoHolder, you will do the following
- Initialize the local cache directory
- Default false to initialize the service from the local cache according to configuration
- Create a FailoverReactor and hold a ServiceInfoHolder to each other
// ServiceInfoHolder public ServiceInfoHolder(String namespace, Properties properties) { initCacheDir(namespace, properties); if (isLoadCacheAtStart(properties)) { = new ConcurrentHashMap<>(()); } else { = new ConcurrentHashMap<>(16); } = new FailoverReactor(this, cacheDir); = isPushEmptyProtect(properties); }
Local cache directory
Local cache writing will be performed in processServiceInfo, which is actually written to this directory (this directory is initialized according to configuration when creating ServiceInfoHolder), so the data in the directory is normally the latest service information read.
// ServiceInfoHolder public ServiceInfo processServiceInfo(ServiceInfo serviceInfo) { .... // Record Service local files (serviceInfo, cacheDir); .... }
The default path of the local cache directory: ${}/nacos/naming/public, can also be customized, customized through ("")
2. FailoverReactor
In the ServiceInfoHolder constructor method, a FailoverReactor class is also initialized, which is also a member variable of ServiceInfoHolder. The function of FailoverReactor is to deal with failover.
The construct failover does the following:
// FailoverReactor public FailoverReactor(ServiceInfoHolder serviceInfoHolder, String cacheDir) { // Hold the reference to ServiceInfoHolder = serviceInfoHolder; // Splicing fault directory: ${}/nacos/naming/public/failover = cacheDir + FAILOVER_DIR; // Initialize the executorService, support delayed execution = new ScheduledThreadPoolExecutor(1, new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); // Daemon thread mode run (true); (""); return thread; } }); // Other initialization operations, enable multiple timing tasks execution through the executorService (); }
Init method execution
Three timing tasks are enabled in this method, and these three tasks are actually the internal classes of FailoverReactor
1. Execute the initialization immediately, and then execute SwitchRefresher every 5 seconds.
2. Initialization delay is 30 minutes, execution interval is 24 hours, and the task is executed.
3. Initialization is executed immediately, the execution interval is 10 seconds, and the core operation is DiskFileWriter
// FailoverReactor public void init() { // Initialization is executed immediately, the execution interval is 5 seconds, and the task is executed by SwitchRefresher (new SwitchRefresher(), 0L, 5000L, ); // Initialization delay is 30 minutes, execution interval is 24 hours, and the task is executed. (new DiskFileWriter(), 30, DAY_PERIOD_MINUTES, ); // If the fault directory is empty after 10 seconds, perform the DiskFileWriter task to force backup (new Runnable() { @Override public void run() { try { File cacheDir = new File(failoverDir); ... File[] files = (); if (files == null || <= 0) { new DiskFileWriter().run(); } } catch (Throwable e) { NAMING_LOGGER.error("[NA] failed to backup file on startup.", e); } } }, 10000L, ); }
DiskFileWriter disk swipe task
Write ServiceInfo to backup disk
// FailoverReactor class DiskFileWriter extends TimerTask { @Override public void run() { Map<String, ServiceInfo> map = (); for (<String, ServiceInfo> entry : ()) { ServiceInfo serviceInfo = (); ... // Write cache to disk (serviceInfo, failoverDir); } } }
SwitchRefresher switches memory flags based on flag failover file
- If the failover file does not exist, it will be returned directly (file switch)
- Compare file modification time, and if it has been modified, get the contents in the failover file.
- The 0 and 1 identities are stored in the failover file. 0 means off, 1 means on.
- When enabled, the thread FailoverFileReader is executed.
// FailoverReactor class SwitchRefresher implements Runnable { long lastModifiedMillis = 0L; @Override public void run() { try { File switchFile = new File(failoverDir + UtilAndComs.FAILOVER_SWITCH); // Exit if the file does not exist if (!()) { ... return; } long modified = (); if (lastModifiedMillis < modified) { lastModifiedMillis = modified; // Get the contents of the failover file String failover = (failoverDir + UtilAndComs.FAILOVER_SWITCH, ().toString()); if (!(failover)) { String[] lines = (()); for (String line : lines) { String line1 = (); // 1 means to enable failover mode if (IS_FAILOVER_MODE.equals(line1)) { (FAILOVER_MODE_PARAM, ()); new FailoverFileReader().run(); // 0 means to turn off failover mode } else if (NO_FAILOVER_MODE.equals(line1)) { (FAILOVER_MODE_PARAM, ()); } } } else { (FAILOVER_MODE_PARAM, ()); } } } catch (Throwable e) { NAMING_LOGGER.error("[NA] failed to read failover switch.", e); } } }
Read backup files after FailoverFileReader failure
This task is the core of failover. The basic operation of failover file reading is to read the content of the backup service information file stored in the failover directory, then convert it into ServiceInfo, and store all ServiceInfo in the ServiceMap property of the FailoverReactor.
The process is as follows:
1. Read all files in the failover directory and traverse them
2. Skip if the file does not exist
3. If the file is a failover switch flag file skipped
4. Read the backup content in the file and convert it into ServiceInfo object
5. Put ServiceInfo object into domMap
6. Finally, determine that domMap is not empty and assign it to 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 = (); if (files == null) { return; } for (File file : files) { // If it is a failover flag file, skip if (().equals(UtilAndComs.FAILOVER_SWITCH)) { continue; } ServiceInfo dom = new ServiceInfo(()); BufferedReader reader = null; try { String dataString = ConcurrentDiskUtil .getFileContent(file, ().toString()); reader = new BufferedReader(new StringReader(dataString)); String json = (); dom = (json, ); } finally { (); } if (!(())) { ((), dom); } } // Read into cache if (() > 0) { serviceMap = domMap; } } }·
Use
Our getServiceInfo method in ServiceInfoHolder will judge that if it is currently in a failover state, it will obtain the service from the FailoverReactor, so this is the cached service.
// ServiceInfoHolder public ServiceInfo getServiceInfo(final String serviceName, final String groupName, final String clusters) { ... if (()) { return (key); } return (key); }
// FailoverReactor public ServiceInfo getService(String key) { ServiceInfo serviceInfo = (key); if (serviceInfo == null) { serviceInfo = new ServiceInfo(); (key); } return serviceInfo; }
Summarize
The above is personal experience. I hope you can give you a reference and I hope you can support me more.