当前位置: 首页 > news >正文

Nacos配置拉取及配置动态刷新原理【源码阅读】

Nacos配置拉取及配置刷新原理

一、初始化时获取配置文件

背景

SpringCloud项目中SpringBoot在启动阶段除了会创建SpringBoot容器,还会通过bootstrap.yml构建一个SpringCloud容器,之后会在准备上下文阶段通过SPI加载实现类后,会进行配置合并。

NacosPropertySourceLocator类

1、该类为拉取nacos配置文件的核心类,在结果SPI加载时NacosPropertySourceLocator作为PropertySourceLocator的实现类,也将被加载到PropertySourceBootstrapConfiguration中参加遍历。

image-20230311052048583

2、最后来到NacosPropertySourceLocator类的locate方法,在该方法中会通过nacosConfigManager来获取ConfigService接口实现类

image-20230311052539842

3、该方法结果调用最后会通过反射获取NacosConfigService对象的有参构造器创建并返回

image-20230311052913557

4、回到上面的locate方法,最后开始加载配置文件,此处会加载三种类型配置文件,分别为共享配置文件、拓展配置文件、应用自身的配置文件。此时由于顺序的原因导致覆盖我们可以得出优先级为从下至上,而其中每一种配置文件的加载都会有三种加载方式,分别是从本地获取,再从nacos获取,为从nacos获取时网络异常等原因导致获取失败,最后一种获取方式为从快照获取(快照的来源为每一种成功从nacos获取后保存)。

image-20230311054017540

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属性构造。该属性即为配置刷新的核心。

image-20230311055333689

2.2、来到ClientWorker构造方法,我们可以看到其中有两个周期线程池,其中executor线程池就是为了控制发出配置检测的间隔,为10秒一次,断开连接默认为3秒

image-20230311061429849

image-20230311061628943

2.3、接下来我们看到发出配置检测请求的checkConfigInfo()方法

image-20230311061728396

ParamUtil.getPerTaskConfigSize()的值为3000,currentLongingTaskCount默认为0,所以正常情况下都只是循环一次

2.4、接下来我们点到LongPollingRunnable看着像长轮询的类,该类实现了Runnable接口,查看其run方法。

以下是使用本地配置时检测是否更新

image-20230311062409691

2.5、然后开始检测并获得远程的变化了的key

image-20230311063653075

2.6、然后获取到对应的value设置新值且计算md5,md5即用于比较是否发现变化,注意其中的setContent方法

public void setContent(String content) {this.content = content;this.md5 = getMd5String(this.content);
}

image-20230311063839977

2.7、随后开始检测并开启下一次任务

image-20230311064047195

2.8、接下来我们打开检测是否变化的方法checkListenerMd5()

image-20230311064134822

2.9、发现发生变化后将会调用safeNotifyListener方法,safeNotifyListener方法则会触发刷新的方法。

补充

  • 3.1、在介绍该方法之前我们补充以下之前遗漏的初始化监听器的步骤,在springboot准备完环境后将会发出ApplicationReadyEvent事件。而在NacosConfigAutoConfiguration配置类中注入了一个NacosContextRefresher对象

image-20230311065040389

  • 3.2、我们打开NacosContextRefresher对象,看到该类实现了ApplicationListener接口,并且监听事件刚好为上步骤中提到的ApplicationReadyEvent事件。

image-20230311065209722

  • 3.3、接下来我们看看他的监听方法中做了什么,看到使用了CAS来控制registerNacosListenersForApplications方法的调用来注册Nacos监听器

image-20230311065244186

  • 3.4、接下来接着查看registerNacosListenersForApplications方法如何绑定的监听器,我们看到是调用registerNacosListener()方法给每一个dataId绑定监听器。

image-20230311065910627

  • 3.5、接下来我们点到registerNacosListener方法中,看到给使用dataId与groupId生成的key绑定一个AbstractSharedListener抽象实现类的监听器,需要注意的时候该实现类中的实现的innerReceive()方法会发出一个RefreshEvent事件,该事件会触发带有@RefreshScope注解的类刷新。

image-20230311070043576

  • 3.6、至此我们看到了绑定监听器的过程,接下来我们回到之前的地方。

2.10、此时回到CacheData类的checkListenerMd5()方法中,该方法调用了safeNotifyListener()方法

image-20230311070826494

2.11、打开safeNotifyListener()方法,看到Listener被强转为AbstractSharedListener修改了其中的dataId与group

image-20230311070932641

2.12、随后调用了AbstractSharedListener类的receiveConfigInfo()方法,该方法中我们在上文提到过该方法中会发布出一个RefreshEvent事件,该事件会触发带有@RefreshScope注解的类刷新。

image-20230311071319772

  • @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的装配机制就是新的属性了

相关文章:

Nacos配置拉取及配置动态刷新原理【源码阅读】

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

第十届省赛——9等差数列(集合做法)

题目&#xff1a;试题 I: 等差数列时间限制: 1.0s 内存限制: 512.0MB 本题总分&#xff1a;25 分【问题描述】数学老师给小明出了一道等差数列求和的题目。但是粗心的小明忘记了一部分的数列&#xff0c;只记得其中 N 个整数。现在给出这 N 个整数&#xff0c;小明想知道包含这…...

《数据分析-JiMuReport03》JiMuReport报表设计入门介绍-新建报表

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

从功能测试进阶自动化测试,爆肝7天整理出这一份超全学习指南【附网盘资源】

因为我最近在分享自动化测试技术&#xff0c;经常被问到&#xff1a;功能测试想转自动化&#xff0c;请问应该怎么入手&#xff1f;有没有好的资源推荐&#xff1f;那么&#xff0c;接下来我就结合自己的经历聊一聊我是如何在工作中做自动化测试的。&#xff08;学习路线和网盘…...

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的模块&#xff0c;可以直接利用pip安装; 其功能类似于pycharm中 python控制台&#xff0c; 而使用ipdb 的优点&#xff0c;便是直接在代码中调试&#xff0c; 避免了在python控制台&#xff0c;或者重新设置一些简单变量。…...

【数据库】聊聊MySQL的日志,binlog、undo log、redo log

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

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是一个分布式、多分区、基于发布/订阅模式的消息队列&#xff08;Message Queue&#xff09;&#xff0c;具有可扩展和高吞吐率的特点。 kafka中大致包含以下部分&#xff1a; Producer&#xff1a; 消息生产者&#xff0c;向 Kafka Broker 发消息的客户…...

《JavaEE》进程和线程的区别和联系

&#x1f451;作者主页&#xff1a;Java冰激凌 &#x1f4d6;专栏链接&#xff1a;JavaEE 目录 进程是什么&#xff1f; 线程是什么&#xff1f; 进程和线程之间的联系~ ps1&#xff1a;假设我们当前的大兴国际机场有一条登机口可以登入飞机 ps2&#xff1a;我们为…...

Matlab生成sinc信号

Matlab生成sinc信号 在Matlab中生成sinc信号非常容易。首先&#xff0c;我们需要了解什么是sinc波形。 sinc波形是一种理想的信号&#xff0c;它在时域上是一个宽度为无穷的矩形函数&#xff0c;而在频域上则是一个平的频谱。它的公式为&#xff1a; sinc⁡(x)sin⁡(πx)πx\…...

进程与线程区别与联系

进程与线程的区别与联系线程线程介绍为什么要有线程呢?线程与进程的区别于联系(重点)线程 线程介绍 我们知道进程就是运行起来的程序, 那线程又是什么呢? 一个线程就是一个 “执行流”. 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 “同时” 执行着多份代码. …...

使用vbscript.regexp实现VBA代码格式化

Office自带的VBE在编辑代码时&#xff0c;没有自动完成代码缩进的功能&#xff0c;而我们在网上找到的VBA代码&#xff0c;经常没有实现良好的自动缩进&#xff0c;复制到VBE后&#xff0c;可读性较差。本文介绍的宏&#xff0c;通过使用vbscript.regexp对象&#xff0c;利用正…...

选择结构习题:百分值转换成其相应的等级

Description 编一程序&#xff0c;输入一个百分制的成绩(整数类型)&#xff0c;按要求输出相应的字符串信息&#xff0c;对应关系为&#xff1a;     excellent 90-100     good 80-89     middle 70-79     pass 60-69 fail 60以下或100以上 Input 输入仅一行&…...

c# 源生成器

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

[N1CTF 2018]eating_cms1

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

数据结构与算法基础(王卓)(15):KMP算法详解(含速成套路和详细思路剖析)

如果时间不够&#xff0c;急&#xff08;忙&#xff09;着应付考试没心思看&#xff0c;直接参考&#xff08;照抄&#xff09;如下套路&#xff1a; PART 1&#xff1a;关于next [ j ] PPT&#xff1a;P30 根据书上以及视频上给出的思路&#xff08;提醒&#xff09;&#x…...

【互联网架构】聊一聊所谓的“跨语言、跨平台“

文章目录序跨语言跨平台【饭后杂谈】为什么有人说Java的跨平台很鸡肋&#xff1f;序 很多技术都具有跨语言、跨平台的特点 比如JSON是跨语言的、Java是跨平台的、UniAPP、Electron是跨平台的 跨语言和跨平台&#xff0c;是比较重要的一个特性。这些特性经常能够决定开发者是否…...

1.JVM常识之 类加载器

1.jvm组成 JVM组成&#xff1a; 1.类加载器 2.运行时数据区 3.执行引擎 4.本地库接口 各组件的作用&#xff1a; 首先通过类加载器&#xff08;ClassLoader&#xff09;会把 Java 代码转换成字节码&#xff0c;运行时数据区&#xff08;Runtime Data Area&#xff09;再把字节码…...

一天搞定《AI工程师的PySide2 PyQt5实战开发手册》

PySide2/PySide6、PyQt5/PyQt6&#xff1a;都是基于Qt 的Python库&#xff0c;可以形象地这样说&#xff0c;PySide2 是Qt的 亲儿子(Qt官方开发的) &#xff0c; PyQt5 是Qt还没有亲儿子之前的收的 义子 &#xff08;Riverbank Computing这个公司开发的&#xff0c;有商业版权限…...

测试微信模版消息推送

进入“开发接口管理”--“公众平台测试账号”&#xff0c;无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息&#xff1a; 关注测试号&#xff1a;扫二维码关注测试号。 发送模版消息&#xff1a; import requests da…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

基础测试工具使用经验

背景 vtune&#xff0c;perf, nsight system等基础测试工具&#xff0c;都是用过的&#xff0c;但是没有记录&#xff0c;都逐渐忘了。所以写这篇博客总结记录一下&#xff0c;只要以后发现新的用法&#xff0c;就记得来编辑补充一下 perf 比较基础的用法&#xff1a; 先改这…...

React19源码系列之 事件插件系统

事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI

前一阵子在百度 AI 开发者大会上&#xff0c;看到基于小智 AI DIY 玩具的演示&#xff0c;感觉有点意思&#xff0c;想着自己也来试试。 如果只是想烧录现成的固件&#xff0c;乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外&#xff0c;还提供了基于网页版的 ESP LA…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

AI,如何重构理解、匹配与决策?

AI 时代&#xff0c;我们如何理解消费&#xff1f; 作者&#xff5c;王彬 封面&#xff5c;Unplash 人们通过信息理解世界。 曾几何时&#xff0c;PC 与移动互联网重塑了人们的购物路径&#xff1a;信息变得唾手可得&#xff0c;商品决策变得高度依赖内容。 但 AI 时代的来…...

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中&#xff0c;我们渴望一个能激发创想、愉悦感官的工作与生活伙伴&#xff0c;它不仅是冰冷的科技工具&#xff0c;更能触动我们内心深处的细腻情感。正是在这样的期许下&#xff0c;华硕a豆14 Air香氛版翩然而至&#xff0c;它以一种前所未有的方式&#x…...

Python ROS2【机器人中间件框架】 简介

销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)

题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...