Nacos配置拉取及配置动态刷新原理【源码阅读】
Nacos配置拉取及配置刷新原理
一、初始化时获取配置文件
背景
SpringCloud项目中SpringBoot在启动阶段除了会创建SpringBoot容器,还会通过bootstrap.yml构建一个SpringCloud容器,之后会在准备上下文阶段通过SPI加载实现类后,会进行配置合并。
NacosPropertySourceLocator类
1、该类为拉取nacos配置文件的核心类,在结果SPI加载时NacosPropertySourceLocator作为PropertySourceLocator的实现类,也将被加载到PropertySourceBootstrapConfiguration中参加遍历。
2、最后来到NacosPropertySourceLocator类的locate方法,在该方法中会通过nacosConfigManager来获取ConfigService接口实现类
3、该方法结果调用最后会通过反射获取NacosConfigService对象的有参构造器创建并返回
4、回到上面的locate方法,最后开始加载配置文件,此处会加载三种类型配置文件,分别为共享配置文件、拓展配置文件、应用自身的配置文件。此时由于顺序的原因导致覆盖我们可以得出优先级为从下至上,而其中每一种配置文件的加载都会有三种加载方式,分别是从本地获取,再从nacos获取,为从nacos获取时网络异常等原因导致获取失败,最后一种获取方式为从快照获取(快照的来源为每一种成功从nacos获取后保存)。
5、最后都将使用上文的NacosConfigService对象getConfigInner方法实现三种方式的获取
private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {group = blank2defaultGroup(group);ParamUtils.checkKeyParam(dataId, group);ConfigResponse cr = new ConfigResponse();cr.setDataId(dataId);cr.setTenant(tenant);cr.setGroup(group);// 优先使用本地配置String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);if (content != null) {LOGGER.warn("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}, config={}", agent.getName(),dataId, group, tenant, ContentUtils.truncateContent(content));cr.setContent(content);String encryptedDataKey = LocalEncryptedDataKeyProcessor.getEncryptDataKeyFailover(agent.getName(), dataId, group, tenant);cr.setEncryptedDataKey(encryptedDataKey);configFilterChainManager.doFilter(null, cr);content = cr.getContent();return content;}try {ConfigResponse response = worker.getServerConfig(dataId, group, tenant, timeoutMs);cr.setContent(response.getContent());cr.setEncryptedDataKey(response.getEncryptedDataKey());configFilterChainManager.doFilter(null, cr);content = cr.getContent();return content;} catch (NacosException ioe) {if (NacosException.NO_RIGHT == ioe.getErrCode()) {throw ioe;}LOGGER.warn("[{}] [get-config] get from server error, dataId={}, group={}, tenant={}, msg={}",agent.getName(), dataId, group, tenant, ioe.toString());}LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}", agent.getName(),dataId, group, tenant, ContentUtils.truncateContent(content));content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);cr.setContent(content);String encryptedDataKey = LocalEncryptedDataKeyProcessor.getEncryptDataKeyFailover(agent.getName(), dataId, group, tenant);cr.setEncryptedDataKey(encryptedDataKey);configFilterChainManager.doFilter(null, cr);content = cr.getContent();return content;
}
6、最后将配置文件中的信息都保存到composite中返回。
以上为项目启动时获取的配置文件流程
配置刷新原理
背景
当远程 Nacos Config Server 中的配置信息发生了变更,我们springboot应用作为Nacos Config Client 是如何感知到的呢?
此处我们需要知道一点前置知识,那就是两个数据消费模型,push模型和pull模型。
- Push 模型:当 Server 端的数据发生了变更,其会主动将更新推送给 Client。Push 模型 适合于 Client 数量不多,且 Server 端数据变化比较频繁的场景。其实时性较好,但其需要维护长连接,占用系统资源。
- Pull 模型:需要 Client 定时查看 Server 端数据是否更新。其实时性不好,且可能会产生数据更新的丢失。
那nacos采用的是哪种模型呢?
其实都不是,Nacos采用了长轮询模型实现的变更通知。
那什么是长轮询呢?
- 长轮询模型整合了 Push 与 Pull 模型的优势。Client 仍定时发起 Pull 请求,查看 Server 端数据是否更新。若发生了更新,则 Server 立即将更新数据以响应的形式发送给 Client 端; 若没有发生更新,Server 端不会发送任何信息,但其会临时性的保持住这个连接一段时间。 若在此时间段内,Server 端数据发生了变更,这个变更就会触发 Server 向 Client 发送变更结 果。这次发送的执行,就是因为长连接的存在。若此期间仍未发生变更,则放弃这个连接。 等待着下一次 Client 的 Pull 请求。 长轮询模型,是 Push 与 Pull 模型的整合,既减少了 Push 模型中长连接的被长时间维护 的时间,又降低了 Pull 模型实时性较差的问题。
- Nacos断开连接默认为3秒
2.1、在上文第2、3步我们提到NacosPropertySourceLocator会通过反射获取到NacosConfigService类的全程构造方法并且构造出NacosConfigService对象。此时我们来看看NacosConfigService类的构造方法。注意该方法中我画绿圈的worker属性构造。该属性即为配置刷新的核心。
2.2、来到ClientWorker构造方法,我们可以看到其中有两个周期线程池,其中executor线程池就是为了控制发出配置检测的间隔,为10秒一次,断开连接默认为3秒
2.3、接下来我们看到发出配置检测请求的checkConfigInfo()方法
ParamUtil.getPerTaskConfigSize()的值为3000,currentLongingTaskCount默认为0,所以正常情况下都只是循环一次
2.4、接下来我们点到LongPollingRunnable看着像长轮询的类,该类实现了Runnable接口,查看其run方法。
以下是使用本地配置时检测是否更新
2.5、然后开始检测并获得远程的变化了的key
2.6、然后获取到对应的value设置新值且计算md5,md5即用于比较是否发现变化,注意其中的setContent方法
public void setContent(String content) {this.content = content;this.md5 = getMd5String(this.content);
}
2.7、随后开始检测并开启下一次任务
2.8、接下来我们打开检测是否变化的方法checkListenerMd5()
2.9、发现发生变化后将会调用safeNotifyListener方法,safeNotifyListener方法则会触发刷新的方法。
补充
- 3.1、在介绍该方法之前我们补充以下之前遗漏的初始化监听器的步骤,在springboot准备完环境后将会发出ApplicationReadyEvent事件。而在NacosConfigAutoConfiguration配置类中注入了一个NacosContextRefresher对象
- 3.2、我们打开NacosContextRefresher对象,看到该类实现了ApplicationListener接口,并且监听事件刚好为上步骤中提到的ApplicationReadyEvent事件。
- 3.3、接下来我们看看他的监听方法中做了什么,看到使用了CAS来控制registerNacosListenersForApplications方法的调用来注册Nacos监听器
- 3.4、接下来接着查看registerNacosListenersForApplications方法如何绑定的监听器,我们看到是调用registerNacosListener()方法给每一个dataId绑定监听器。
- 3.5、接下来我们点到registerNacosListener方法中,看到给使用dataId与groupId生成的key绑定一个AbstractSharedListener抽象实现类的监听器,需要注意的时候该实现类中的实现的innerReceive()方法会发出一个RefreshEvent事件,该事件会触发带有@RefreshScope注解的类刷新。
- 3.6、至此我们看到了绑定监听器的过程,接下来我们回到之前的地方。
2.10、此时回到CacheData类的checkListenerMd5()方法中,该方法调用了safeNotifyListener()方法
2.11、打开safeNotifyListener()方法,看到Listener被强转为AbstractSharedListener修改了其中的dataId与group
2.12、随后调用了AbstractSharedListener类的receiveConfigInfo()方法,该方法中我们在上文提到过该方法中会发布出一个RefreshEvent事件,该事件会触发带有@RefreshScope注解的类刷新。
-
@RefreshScope概述
- @RefreshScope注解标注了@Scope注解,井默认了ScopedProxyMode.TARGET_CLASS
属性,此属性的功能就是创建一个代理,在每次调用的时候都用它来调用GenericScope#get方法来获取bean对象 - 在GenericScope里面包装了一个内部类BeanLifecycleWrapperCache来对加了
@RefreshScope的bean进行缓存,使其在不刷新时获取的都是同一个对象 - 如属性发生变更会调用ContextRefresher#frefresh()一>RefreshScope#refreshAll()进行缓存
清理方法调用,并发送刷新事件通知一>调用GenericScope#destroy()实现清理缓存 - 当下一次使用此bean对象时,代理对象会调用GenericScoper#get(String name,
ObjectFactory<?>objectFactory)方法创建一个新的bean对象,井存入缓存中,此时新对象因为Spring的装配机制就是新的属性了
- @RefreshScope注解标注了@Scope注解,井默认了ScopedProxyMode.TARGET_CLASS
相关文章:

Nacos配置拉取及配置动态刷新原理【源码阅读】
Nacos配置拉取及配置刷新原理 一、初始化时获取配置文件 背景 SpringCloud项目中SpringBoot在启动阶段除了会创建SpringBoot容器,还会通过bootstrap.yml构建一个SpringCloud容器,之后会在准备上下文阶段通过SPI加载实现类后,会进行配置合并…...

第十届省赛——9等差数列(集合做法)
题目:试题 I: 等差数列时间限制: 1.0s 内存限制: 512.0MB 本题总分:25 分【问题描述】数学老师给小明出了一道等差数列求和的题目。但是粗心的小明忘记了一部分的数列,只记得其中 N 个整数。现在给出这 N 个整数,小明想知道包含这…...

《数据分析-JiMuReport03》JiMuReport报表设计入门介绍-新建报表
报表设计 1 新建报表 1.1 创建新的数据报表 以数据报表为例,简单介绍创建报表的过程 1.2 进入报表设计页面 如下图可见,主要分为四个模块: 模块一(左) 数据集管理报表信息数据字典 模块二(右) 这部分是对数据报表的进一步优化 模块三(上…...

从功能测试进阶自动化测试,爆肝7天整理出这一份超全学习指南【附网盘资源】
因为我最近在分享自动化测试技术,经常被问到:功能测试想转自动化,请问应该怎么入手?有没有好的资源推荐?那么,接下来我就结合自己的经历聊一聊我是如何在工作中做自动化测试的。(学习路线和网盘…...

CNN神经网络——手写体识别
目录 Load The Datesets Defining,Training,Measuring CNN Algorithm Datasets GRAET HONOR TO SHARE MY KNOWLEDGE WITH YOU This paper is going to show how to use keras to relize a CNN model for digits classfication Load The Datesets The datasets files are …...

python调试模块ipdb
1. 调试python ipdb是用来python中用以交互式debug的模块,可以直接利用pip安装; 其功能类似于pycharm中 python控制台, 而使用ipdb 的优点,便是直接在代码中调试, 避免了在python控制台,或者重新设置一些简单变量。…...

【数据库】聊聊MySQL的日志,binlog、undo log、redo log
日志 在数据库中,如何保证数据的回滚,以及数据同步,系统宕机后可以恢复到原来的状态,其实就是依靠日志。 其中bin log是Server层特有的,redo log是Innodb存储引擎特有的。 bin log 是逻辑日志,主要记录这条…...

aws dynamodb java低等级api和高级客户端api的使用
参考资料 https://docs.amazonaws.cn/zh_cn/sdk-for-java/latest/developer-guide/setup-project-maven.html 初始化环境 创建maven项目 mvn org.apache.maven.plugins:maven-archetype-plugin:3.1.2:generate \-DarchetypeArtifactId"maven-archetype-quickstart&quo…...

Kafka中那些巧妙的设计
一、kafka的架构 Kafka是一个分布式、多分区、基于发布/订阅模式的消息队列(Message Queue),具有可扩展和高吞吐率的特点。 kafka中大致包含以下部分: Producer: 消息生产者,向 Kafka Broker 发消息的客户…...

《JavaEE》进程和线程的区别和联系
👑作者主页:Java冰激凌 📖专栏链接:JavaEE 目录 进程是什么? 线程是什么? 进程和线程之间的联系~ ps1:假设我们当前的大兴国际机场有一条登机口可以登入飞机 ps2:我们为…...

Matlab生成sinc信号
Matlab生成sinc信号 在Matlab中生成sinc信号非常容易。首先,我们需要了解什么是sinc波形。 sinc波形是一种理想的信号,它在时域上是一个宽度为无穷的矩形函数,而在频域上则是一个平的频谱。它的公式为: sinc(x)sin(πx)πx\…...
进程与线程区别与联系
进程与线程的区别与联系线程线程介绍为什么要有线程呢?线程与进程的区别于联系(重点)线程 线程介绍 我们知道进程就是运行起来的程序, 那线程又是什么呢? 一个线程就是一个 “执行流”. 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 “同时” 执行着多份代码. …...
使用vbscript.regexp实现VBA代码格式化
Office自带的VBE在编辑代码时,没有自动完成代码缩进的功能,而我们在网上找到的VBA代码,经常没有实现良好的自动缩进,复制到VBE后,可读性较差。本文介绍的宏,通过使用vbscript.regexp对象,利用正…...
选择结构习题:百分值转换成其相应的等级
Description 编一程序,输入一个百分制的成绩(整数类型),按要求输出相应的字符串信息,对应关系为: excellent 90-100 good 80-89 middle 70-79 pass 60-69 fail 60以下或100以上 Input 输入仅一行&…...

c# 源生成器
本文概述了 .NET Compiler Platform(“Roslyn”)SDK 附带的源生成器。 通过源生成器,C# 开发人员可以在编译用户代码时检查用户代码。 生成器可以动态创建新的 C# 源文件,这些文件将添加到用户的编译中。 这样,代码可以…...

[N1CTF 2018]eating_cms1
一个cms,先打开环境试了一下弱口令,无效,再试一下万能密码,告诉我有waf,先不想怎么绕过,直接开扫(信息收集)访问register.php注册一个账号进行登录上面的链接尝试用php读文件http://…...

数据结构与算法基础(王卓)(15):KMP算法详解(含速成套路和详细思路剖析)
如果时间不够,急(忙)着应付考试没心思看,直接参考(照抄)如下套路: PART 1:关于next [ j ] PPT:P30 根据书上以及视频上给出的思路(提醒)&#x…...

【互联网架构】聊一聊所谓的“跨语言、跨平台“
文章目录序跨语言跨平台【饭后杂谈】为什么有人说Java的跨平台很鸡肋?序 很多技术都具有跨语言、跨平台的特点 比如JSON是跨语言的、Java是跨平台的、UniAPP、Electron是跨平台的 跨语言和跨平台,是比较重要的一个特性。这些特性经常能够决定开发者是否…...

1.JVM常识之 类加载器
1.jvm组成 JVM组成: 1.类加载器 2.运行时数据区 3.执行引擎 4.本地库接口 各组件的作用: 首先通过类加载器(ClassLoader)会把 Java 代码转换成字节码,运行时数据区(Runtime Data Area)再把字节码…...

一天搞定《AI工程师的PySide2 PyQt5实战开发手册》
PySide2/PySide6、PyQt5/PyQt6:都是基于Qt 的Python库,可以形象地这样说,PySide2 是Qt的 亲儿子(Qt官方开发的) , PyQt5 是Qt还没有亲儿子之前的收的 义子 (Riverbank Computing这个公司开发的,有商业版权限…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...

LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...

NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...