在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/failoverthis.failoverDir = cacheDir + FAILOVER_DIR;// 初始化executorService,支持延时执行this.executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {@Overridepublic 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秒,执行任务SwitchRefresherexecutorService.scheduleWithFixedDelay(new SwitchRefresher(), 0L, 5000L, TimeUnit.MILLISECONDS);// 初始化延迟30分钟执行,执行间隔24小时,执行任务DiskFileWriterexecutorService.scheduleWithFixedDelay(new DiskFileWriter(), 30, DAY_PERIOD_MINUTES, TimeUnit.MINUTES);// 10秒后如果故障目录为空,则执行DiskFileWriter任务强制备份executorService.schedule(new Runnable() {@Overridepublic 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刷盘任务

将ServiceInfo写入备份磁盘
// FailoverReactor
class DiskFileWriter extends TimerTask {@Overridepublic 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;@Overridepublic void run() {try {File 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 = failover.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 {@Overridepublic 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;
}

Nacos客户端本地缓存和故障转移相关推荐

  1. 06篇 Nacos Client本地缓存及故障转移

    学习不用那么功利,二师兄带你从更高维度轻松阅读源码- 本篇文章我们来通过源码分析一下Nacos的本地缓存及故障转移功能,涉及到核心类为ServiceInfoHolder和FailoverReactor ...

  2. apache cxf_Apache CXF负载平衡和故障转移

    apache cxf 不久前,我们已经面临了基于Apache CXF的负载平衡Web服务客户端的需求. 此外,当某些服务器关闭时,客户端应自动进行故障转移. 更糟糕的是,服务器目标地址列表要从外部服务 ...

  3. 探讨下如何更好的使用缓存 —— 集中式缓存Redis的BitMap存储、管道与事务、以及与本地缓存一起构建多级缓存

    大家好,又见面了. 通过前面的文章,我们一起剖析了Guava Cache.Caffeine.Ehcache等本地缓存框架的原理与使用场景,也一同领略了以Redis为代表的集中式缓存在分布式高并发场景下 ...

  4. Apache CXF负载平衡和故障转移

    前一段时间,我们已经面临基于Apache CXF的负载平衡Web服务客户端的需求. 此外,当某些服务器关闭时,客户端应自动进行故障转移. 更糟糕的是,服务器目标地址列表要从外部服务获取并在运行时更新. ...

  5. 微服务架构:Nacos本地缓存 PK 微服务优雅下线

    前言 在上篇文章<微服务:剖析一下源码,Nacos的健康检查竟如此简单>中讲了当微服务突然挂掉的解放方案:调整健康检查周期和故障请求重试.朋友看了文章,建议再聊聊正常关闭服务时如何让微服务 ...

  6. (2)MongoDB副本集自动故障转移原理(含客户端)

    前文我们搭建MongoDB三成员副本集,了解集群基本特性,今天我们围绕下图聊一聊背后的细节. 默认搭建的副本集均在主节点读写,辅助节点冗余部署,形成高可用和备份,具备自动故障转移能力. 集群心跳保活 ...

  7. php缓存远程图片接口,Android_Android远程获取图片并本地缓存,对于客户端——服务器端应用 - phpStudy...

    Android远程获取图片并本地缓存 对于客户端--服务器端应用,从远程获取图片算是经常要用的一个功能,而图片资源往往会消耗比较大的流量,对应用来说,如果处理不好这个问题,那会让用户很崩溃,不知不觉手 ...

  8. 客户端缓存(http缓存和本地缓存)

    原文链接:https://juejin.im/post/6844904194680291342 http缓存 http缓存用于客户端储存一些不经常变化的静态文件(图片.css.js等).分为强制缓存和 ...

  9. 从Nacos客户端视角来分析一下配置中心实现原理

    目录 一 动态配置 1. 环境准备 2.新建配置 3.导入配置 4.配置客户端 5. 修改配置信息 6.小结 二  配置中心原理(推还是拉) 1.实例化 ConfigService 2.添加 List ...

最新文章

  1. 《爱情公寓2》将播 恶搞宣传片大喊“有种别看”
  2. 微软无解!Win10用户突然减少:装回Win7
  3. c 多线程运行混乱_一篇文章读懂 Python 多线程
  4. RecursionError: maximum recursion depth exceeded
  5. 恭喜我的同事丁宇入选年度 IT 领军人物
  6. python上下文管理关键字_详解 Python 中的 with 与 上下文管理器
  7. linux-RPM与YUM
  8. leetcode 381. O(1) 时间插入、删除和获取随机元素 - 允许重复
  9. Javascript框架库漏洞验证
  10. java中数组的返回值是什么类型_面试必问:Java中String类型为什么设计成不可变的?...
  11. 关于Vue.js的v-for,key的顺序改变,影响过渡动画表现
  12. Java8 新JavaScript脚本引擎Nashorn小试
  13. node-webkit笔记
  14. 1.3 Zend_Acl (3)
  15. pp助手可以刷机吗android,pp助手刷机 pp助手怎么刷机
  16. HTML5如何实现网页消息通知提醒
  17. Java实例(1)BMI计算
  18. 在mac上怎么把png转换成jpg
  19. 今日金融词汇---后复权,是什么?
  20. android隐藏头标题,关于隐藏Android标题栏总结

热门文章

  1. 01:机器人学数学基础
  2. 基于Selenium实现的web自动化测试框架
  3. Arduino重磅更新!IDE 2.0来了
  4. 中国证券登记结算有限责任公司2023年度信息技术专业人员招聘正式启动
  5. 路由器WAN口和LAN口的IP地址的区别
  6. Java多线程—守护线程
  7. bilibili爬虫+数据分析
  8. 3.8关于向WorldWind地球模型添加图层
  9. xhEditor实现插入代码功能
  10. oracle如何查询授权,oracle授权查询的讲解