Nacos客户端本地缓存和故障转移方式

2024-12-28 22:50

本文主要是介绍Nacos客户端本地缓存和故障转移方式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《Nacos客户端本地缓存和故障转移方式》Nacos客户端在从Server获得服务时,若出现故障,会通过ServiceInfoHolder和FailoverReactor进行故障转移,ServiceI...

在Nacos客户端从Server获得服务的时候,在某些时候出现了一些故障, 这时候为了保证服务正常,Nacos进行了故障转移,原理就是将之前缓存的服务信息拿出来用,防止服务出现问题,涉及到的核心类为ServiceInfoHolder和FailoverReactor。

本地缓存有两方面,第一方面是从注册中心获得实例信息会缓存在内存当中,也就是通过Map的形式承载,这样查询操作都方便。第二方面便是通过磁盘文件的形式定时缓存起来,以备不时之需。

故障转移也分两方面,第一方面是故障转移的开关是通过文件来标记的;第二方面是当开启故障转移之后,当发生故障时,可以从故障转移备份的文件中来获得服务实例信息。

1. ServiceInfoHolder

ServiceInfoHolder类,顾名思义,服务信息的持有者。每次客户端从注册中心获取新的服务信息时都会调用该类,其中processServiceInfo方法来进行本地化处理,包括更新缓存服务、发布事件、更新本地文件等。

ServiceInfoHolder类持有了ServiceInfo,通过一个ConcurrentMap来储存

// ServiceInfoHolder
private final ConcurrentMap<String, ServiceInfo> serviceInfoMap;

当从服务端拉会服务信息时,就会往这个map中进行存储。

// ServiceInfoHolder
public ServiceInfo processServiceInfo(ServiceInfo serviceInfo) {
    ....
    //缓存服务信息
    serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
    ....
}

在创建ServiceInfoHolder时会做如下事情

  1. 初始化本地缓存目录
  2. 根据配置从本地缓存初始化服务,默认false
  3. 创建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中会进行本地缓存写入,其实就是写入这个目录(这个目录是在创建ServiceInfoHolder时根据配置初始化的),所以目录中的数据正常是最新读取到的服务信息

// ServiceInfoHolder
public ServiceInfo processServiceInfo(ServiceInfo serviceInfo) {
    ....
    // 记录Service本地文件
    DiskCache.write(serviceInfo, cacheDir);
    ....
}

本地缓存目录默认路径:${user.home}/nacos/naming/public,也可以自定义,通过System.setProperty("JM.SNAPSHOT.PATH")自定义

2. FailoverReactor

在ServiceInfoHolder的构造方法中,还会初始化一个FailoverReactor类,同样是ServiceInfoHolder的成员变量。FailoverReactor的作用便是用来处理故障转移的。

构造故障转移做了如下事情:

// FailoverReactor
public FailoverReactor(ServiceInfoHolder serviceInfoHolder, String cacheDir) {
    // 持有ServiceInfoHolder的引用
    this.serviceInfoHolder = serviceInfoHolder;
    // 拼接故障目录:${user.home}/nacos/naming/public/failover
    this.failoverDir = cacheDir + FAILOVER_DIR;
    // 初始化executorService,支持延时执行
    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开启多个定时任务执行
    this.init();
}

init方法执行

在这个方法中开启了三个定时任务,这三个任务其实都是FailoverReactor的内部类

1. 初始化立即执行,然后每间隔5秒再执行SwitchRefresher

2. 初始化延迟30分钟执行,执行间隔24小时,执行任务DiskFileWriter

3. 初始化立即执行,执行间隔10秒,执行核心操作为DiskFileWriter

// FailoverReactor
public void init() {
    // 初始化立即执行,执行间隔5秒,执行任务SwitchRefresher
    executorService.scheduleWithFixedDelay(new SwitchRefresher(), 0L, 5000L, TimeUnit.MILLISECONDS);
    // 初始化延迟30分钟执行,执行间隔24小时,执行任务DiskFileWriter
    executorService.scheduleWithFixedDelay(new DiskFileWriter(), 30, DAY_PERIOD_MINUTES, TimeUnit.MINUTES);
    // 10秒后如果故障目录为空,则执行DiskFileWriter任务强制备份
    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) {
            php    NAMING_LOGGER.error("[NA] failed to backup file on startup.", e);
            }

        }
    }, 10000L, TimeUnit.MILLISECONDS);
}

DiskFileWriter刷盘任务

将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根据标记故障转移文件切换内存标志

  1. 如果故障转移文件不存在,则直接返回(文件开关)
  2. 比较文件修改时间,如果已经修改,则获取故障转移文件中的内容。
  3. 故障转移文件中存储了0和1标识。0表示关闭,1表示开启。
  4. 当为开启状态时,执行线程FailoverFileReader。
// FailoverReactor
class SwitchRefresher implements Runnable {
    long lastModifiedMillis = 0L;
    @Override
    public void run() {
        try {
            Filhttp://www.chinasem.cne switchFile = new File(failoverDir + UtilAndComs.FAILOVER_SWITCH);
            // 文件不存在则退出
            if (!switchFile.exists()) {
                ...
                return;
            }

            long modified = switchFile.lastModified();
      
            if (lastModifiedMillis < modified) {
                lastModifiedMillis = modified;
                // 获取故障转移文件内容
                String failover = ConcurrentDiskUtil.getFileContent(failoverDir + UtilAndComs.FAILOVER_SWITCH,
                                                                    Charset.defaultCharset().toString());
                if (!StringUtils.isEmpty(failover)) {
                    String[] lines = failovewww.chinasem.cnr.split(DiskCache.getLineSeparator());

                    for (String line : lines) {
                        String line1 = line.trim();
                        // 1 表示开启故障转移模式
                        if (IS_FAILOVER_MODE.equals(line1)) {
                            switchParams.put(FAILOVER_MODE_PARAM, Boolean.TRUE.toString());
                            new FailoverFileReader().run();
                        // 0 表示关闭故障转移模式
                        } 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故障后读取备份文件

该任务是故障转移的核心, 故障转移文件读取,基本操作就是读取failover目录存储的**备份服务信息文件**内容,然后转换成ServiceInfo,并且将所有的ServiceInfo储存在FailoverReactor的ServiceMap属性中。

流程如下:

1. 读取failover目录下的所有文件,进行遍历处理

2. 如果文件不存在跳过

3. 如果文件是故障转移开关标志文件跳过

4. 读取文件中的备份内容,转换为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) {
            // 如果是故障转移标志文件,则跳过
            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方法就会判断,如果当前是故障切换状态,就会从FailoverReactor中获取服务,那么这是就用到了FailoverReactor.serviceMap缓存的服务了

// 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;
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持China编程(www.chinasem.cn)。

这篇关于Nacos客户端本地缓存和故障转移方式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1152822

相关文章

在C#中调用Python代码的两种实现方式

《在C#中调用Python代码的两种实现方式》:本文主要介绍在C#中调用Python代码的两种实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#调用python代码的方式1. 使用 Python.NET2. 使用外部进程调用 Python 脚本总结C#调

Vue中组件之间传值的六种方式(完整版)

《Vue中组件之间传值的六种方式(完整版)》组件是vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用,针对不同的使用场景,如何选择行之有效的通信方式... 目录前言方法一、props/$emit1.父组件向子组件传值2.子组件向父组件传值(通过事件形式)方

Python实现Microsoft Office自动化的几种方式及对比详解

《Python实现MicrosoftOffice自动化的几种方式及对比详解》办公自动化是指利用现代化设备和技术,代替办公人员的部分手动或重复性业务活动,优质而高效地处理办公事务,实现对信息的高效利用... 目录一、基于COM接口的自动化(pywin32)二、独立文件操作库1. Word处理(python-d

Java 中实现异步的多种方式

《Java中实现异步的多种方式》文章介绍了Java中实现异步处理的几种常见方式,每种方式都有其特点和适用场景,通过选择合适的异步处理方式,可以提高程序的性能和可维护性,感兴趣的朋友一起看看吧... 目录1. 线程池(ExecutorService)2. CompletableFuture3. ForkJoi

MySQL中慢SQL优化的不同方式介绍

《MySQL中慢SQL优化的不同方式介绍》慢SQL的优化,主要从两个方面考虑,SQL语句本身的优化,以及数据库设计的优化,下面小编就来给大家介绍一下有哪些方式可以优化慢SQL吧... 目录避免不必要的列分页优化索引优化JOIN 的优化排序优化UNION 优化慢 SQL 的优化,主要从两个方面考虑,SQL 语

Linux系统之主机网络配置方式

《Linux系统之主机网络配置方式》:本文主要介绍Linux系统之主机网络配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、查看主机的网络参数1、查看主机名2、查看IP地址3、查看网关4、查看DNS二、配置网卡1、修改网卡配置文件2、nmcli工具【通用

Android自定义Scrollbar的两种实现方式

《Android自定义Scrollbar的两种实现方式》本文介绍两种实现自定义滚动条的方法,分别通过ItemDecoration方案和独立View方案实现滚动条定制化,文章通过代码示例讲解的非常详细,... 目录方案一:ItemDecoration实现(推荐用于RecyclerView)实现原理完整代码实现

C语言实现两个变量值交换的三种方式

《C语言实现两个变量值交换的三种方式》两个变量值的交换是编程中最常见的问题之一,以下将介绍三种变量的交换方式,其中第一种方式是最常用也是最实用的,后两种方式一般只在特殊限制下使用,需要的朋友可以参考下... 目录1.使用临时变量(推荐)2.相加和相减的方式(值较大时可能丢失数据)3.按位异或运算1.使用临时

SpringCloud之consul服务注册与发现、配置管理、配置持久化方式

《SpringCloud之consul服务注册与发现、配置管理、配置持久化方式》:本文主要介绍SpringCloud之consul服务注册与发现、配置管理、配置持久化方式,具有很好的参考价值,希望... 目录前言一、consul是什么?二、安装运行consul三、使用1、服务发现2、配置管理四、数据持久化总

SpringBoot @Scheduled Cron表达式使用方式

《SpringBoot@ScheduledCron表达式使用方式》:本文主要介绍SpringBoot@ScheduledCron表达式使用方式,具有很好的参考价值,希望对大家有所帮助,如有... 目录Cron 表达式详解1. 表达式格式‌2. 特殊字符解析3. 常用示例‌4. 重点规则5. 动态与复杂场景‌