Nacos——配置中心源码详解

news2025/7/11 15:17:10

Nacos——配置中心源码详解

  • 配置中心
    • 客户端主动获取
      • 客户端
      • 服务端
    • 客户端长轮询更新
      • 客户端
        • 1.入口
        • 2.配置文件分片处理
        • 3.配置文件处理
        • 4.本地配置文件与缓存数据的对比
        • 5.开启长轮询与服务端对比
        • 6.通知监听器
        • 7.监听回调处理
      • 服务端
        • 1.入口
        • 2.长轮询机制
        • 3.长轮询的延迟任务
        • 4.数据变更事件
    • 总结

本文基于1.4.3版本
GitHub地址:https://github.com/alibaba/nacos/releases

配置中心

客户端的配置有两种方式来维持,一是客户端主动获取,二是客户端长轮询更新

关于配置文件有几个类型要说明一下:
1.本地配置文件:本地就已经存在的配置文件
2.本地缓存文件:从服务端获取的保存在了本地(本地生成了文件)
3.cacheData缓存数据:内存中缓存的配置文件数据

客户端主动获取

在这里插入图片描述

客户端

ConfigExample中我们可以得知,获取配置的方法为NacosConfigService.getConfig

1.优先从本地配置中获取
2.本地配置中没有则会去服务端获取
3.服务端异常且异常不是因为鉴权失败,则从本地缓存文件中获取

在这里插入图片描述

我们来到ClientWorker.getServerConfig方法里面:
1.发起HTTP请求,URL:/v1/cs/configs
2.请求后不管怎样都会先创建一个本地缓存文件,只不过成功的会带有数据以及文件类型

在这里插入图片描述

服务端

ConfigController.getConfig

在这里插入图片描述

除去前面的参数校验以及处理,直接看ConfigServletInner.doGetConfig方法:

(里面判断分支太多就不截全部了,文件不是读数据库就是读本地文件)

1.获取读锁,获取不到就自旋重复获取10次
2.根据beta、tag、autoTag来判断读什么配置
3.PropertyUtil.isDirectRead()判断是读mysql还是读文件
4.使用jdk的零拷贝传输直接将文件输入流转response输出流
5.释放读锁

在这里插入图片描述

在这里插入图片描述

网上找的doGetConfig方法流程图

在这里插入图片描述

客户端长轮询更新

在这里插入图片描述

客户端

1.入口

NacosConfigService初始化的时候,会初始化两个组件

  • 一是网络组件,也就是http数据处理的(起作用的是ServerHttpAgent
  • 二是客户端的长轮询ClientWorker

在这里插入图片描述

ClientWorker在初始化的时候就会初始化两个定时调度线程池,以及启动一个定时任务,该定时任务会执行ClientWorker.checkConfigInfo() 方法(10ms执行一次):

在这里插入图片描述

2.配置文件分片处理

ClientWorker.checkConfigInfo()

该任务就是用来分配任务的,设定每3000个配置文件为一个分片,每个分片都会开启一个异步任务放到线程池中处理该分片的配置文件,异步任务为LongPollingRunnable

  1. 假设有5000个配置文件,就分为两个分片,开启两个线程处理
  2. 假设有8000个配置文件,就分为三个分片,开启三个线程处理

在这里插入图片描述

3.配置文件处理

LongPollingRunnable.run()

  1. 遍历所有配置,只有该异步任务监听的分片配置才会处理
  2. 对比本地配置文件和缓存的cacheData数据,判断数据是否出现变化
  3. 出现变化则针对监听器发布变更通知
  4. 将该分片监听的配置与服务端的配置对比,找到需要更新的配置Key(dataId+group+namespace)
  5. 遍历这些需要更新的key,然后请求服务端(主动获取的方式),得到最新的cacheData并更新本地缓存文件
  6. 针对监听器发布变更通知
  7. 之后继续执行该任务(轮询)

向服务端对比更新过程就类似于:我先拿着我这个分片所有配置文件的key和内容的MD5去和服务端的配置对比,服务端对比这些文件的MD5后返回需要更新的配置文件key,然后客户端遍历这些需要更新的配置文件key去主动请求服务端获取最新的,然后更新本地缓存及本地缓存的文件

public void run() {
            
            List<CacheData> cacheDatas = new ArrayList<CacheData>();
            List<String> inInitializingCacheList = new ArrayList<String>();
            try {
                // 校验本地配置同时获取同分片配置
                for (CacheData cacheData : cacheMap.values()) {
                    // 同分片的配置才处理
                    if (cacheData.getTaskId() == taskId) {
                        // 将同分片的配置加入集合
                        cacheDatas.add(cacheData);
                        try {
                            //通过本地配置文件和cacheData集合中的数据进行比对,判断是否出现数据变化
                            checkLocalConfig(cacheData);
                            //这里表示数据有变化,需要通知监听器
                            if (cacheData.isUseLocalConfigInfo()) {
                                //通知所有针对当前配置设置了监听的监听器
                                cacheData.checkListenerMd5();
                            }
                        } catch (Exception e) {
                            LOGGER.error("get local config info error", e);
                        }
                    }
                }
                
                // 与服务端对比 找到需要更新的配置key
                List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);
                if (!CollectionUtils.isEmpty(changedGroupKeys)) {
                    LOGGER.info("get changedGroupKeys:" + changedGroupKeys);
                }
                // 遍历发生了变化的key,并根据key去服务端请求最新配置,并更新到内存缓存中
                for (String groupKey : changedGroupKeys) {
                    String[] key = GroupKey.parseKey(groupKey);
                    String dataId = key[0];
                    String group = key[1];
                    String tenant = null;
                    if (key.length == 3) {
                        tenant = key[2];
                    }
                    try {
                        // 从远程服务端获取最新的配置,并缓存到内存中
                        ConfigResponse response = getServerConfig(dataId, group, tenant, 3000L);
                        CacheData cache = cacheMap.get(GroupKey.getKeyTenant(dataId, group, tenant));
                        cache.setEncryptedDataKey(response.getEncryptedDataKey());
                        cache.setContent(response.getContent());
                        if (null != response.getConfigType()) {
                            cache.setType(response.getConfigType());
                        }
                    } catch (NacosException ioe) {
                        String message = String
                                .format("[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s",
                                        agent.getName(), dataId, group, tenant);
                        LOGGER.error(message, ioe);
                    }
                }
                for (CacheData cacheData : cacheDatas) {
                    if (!cacheData.isInitializing() || inInitializingCacheList
                            .contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) {
                        //通知所有针对当前配置设置了监听的监听器
                        cacheData.checkListenerMd5();
                        cacheData.setInitializing(false);
                    }
                }
                inInitializingCacheList.clear();
                // 继续执行该任务
                executorService.execute(this);
            } catch (Throwable e) {
                // If the rotation training task is abnormal, the next execution time of the task will be punished
                LOGGER.error("longPolling error : ", e);
                // 如果任务出现异常,那么下次的执行时间就要加长,类似衰减重试
                executorService.schedule(this, taskPenaltyTime, TimeUnit.MILLISECONDS);
            }
        }

4.本地配置文件与缓存数据的对比

注意:该配置文件是项目启动就存在的,不是通过拉取服务端缓存下来的缓存文件

ClientWorker.checkLocalConfig

这个就好比,我本地已经有该配置文件了然后又在服务端找到了该配置文件,要怎么处理?

1.如果缓存配置为不使用本地配置,但是本地配置又存在,则设置该缓存配置为使用本地配置(本地配置文件的内存写到缓存配置中更新),需要发布变更通知
2.如果缓存配置为使用本地配置,但是本地配置又不存在,则设置该缓存配置为不使用本地配置,不需要通知
3.如果缓存配置为使用本地配置,本地配置也存在,但是缓存配置和本地配置的版本不同,则也需要将本地配置文件的内存写到缓存配置中更新

在这里插入图片描述

5.开启长轮询与服务端对比

ClientWorker.checkUpdateDataIds

就是把需要对比的配置关键信息拼接成字符串发送到服务端对比

在这里插入图片描述

ClientWorker.checkUpdateConfigStr

开启长轮询,处理对比逻辑,长轮询请求URL: v1/ns/configs/listener

在这里插入图片描述

6.通知监听器

CacheData.checkListenerMd5

在该方法中会对比listener和cacheData的MD5,如果不一样则代表cacheData发生了变化,则会触发监听器的回调处理

在这里插入图片描述

那么CacheData的MD5又是什么时候变得呢?在更新content内容的时候就变了,方法内同时更新了MD5
在这里插入图片描述

7.监听回调处理

CacheData.safeNotifyListener

在该方法内,cacheData会遍历所有listener,只要两者MD5不同就会在该方法内触发调用listener.receiveConfigInfo方法

拓展: 我们也可以自定义listener加入进去,在变更的时候做自己的处理:NacosConfigService.addListener调用该方法加入自定义监听器

在这里插入图片描述

服务端

1.入口

直接看长轮询的接口

ConfigController.listener

前面没啥说的就处理字符串数据
在这里插入图片描述

ConfigServletInner.doPollingConfig

这个方法里面其实就是短轮询和长轮询的判断,根据请求头Long-Pulling-Timeout,我们主要看长轮询,短轮询就不看了
在这里插入图片描述

2.长轮询机制

LongPollingService.addLongPollingClient

这个方法主要做几件事:

1.获取客户端请求的超时时间,减去500ms后赋值给timeout变量。
2.判断isFixedPolling,如果为true,定时任务将会在30s后开始执行,否则在29.5s后开始执行
3.和服务端的数据进行MD5对比,如果发送变化则直接返回
4.如果没有变化则将请求转化为异步请求挂起,然后延迟执行ClientLongPolling线程

在这里插入图片描述

3.长轮询的延迟任务

ClientLongPolling.run

内部就一个延迟任务,延迟执行,执行时通过比较MD5判断客户端请求的groupKeys是否发生变更,并将变更结果通过response返回给客户端

所以整个过程来看,服务端就做了两件事,先判断是否配置有变化,有则立刻返回,没有则延迟返回,并在返回时再进行一次对比,这样使得配置在30s内没变化的情况下一直处理连接状态,这就是长轮询机制

在这里插入图片描述

4.数据变更事件

上述有个延迟30s的周期,如果这时间段内配置发生变化了,那配置岂不是得不到及时的更新?

当然不是,注意到上述框出来的allSubs了吗,这是个ClientLongPolling对象的队列,这是干嘛的?

而且从上述逻辑上不难看出,在延迟任务执行前的这30s内ClientLongPolling是一直存在于这个队列中的,因为执行完后就被添加进了这个队列,执行时才会移除,所以我们需要看看这个是干嘛的

顺着思路找我们不难发现,在LongPollingService生成的时候,订阅了一个LocalDataChangeEvent事件,触发这个事件的时候会执行一个DataChangeTask异步任务
在这里插入图片描述

DataChangeTask

1.会遍历队列中所有的ClientLongPolling对象
2.判断请求过来的需要对比的key里面是否包含当前变更的配置key
3.包含则移除出队列,并直接返回响应客户端信息

在这里插入图片描述

这里说明在长轮询的延迟执行的时间内,服务端也没闲着,一直在监听配置的变更,一旦有配置变更则发布LocalDataChangeEvent事件,触发事件后则提前响应客户端

总结

  • 配置分三种形态,本地配置文件,本地缓存文件,本地缓存数据
  • 客户端通过主动拉取和长轮询的方式来获取配置以及更新配置
  • 主动拉取的顺序是本地配置文件→服务端→本地缓存文件
  • 客户端长轮询中对比配置不同的方式是对比本地文件与本地缓存数据的MD5
  • 长轮询是在客户端与服务端对比配置不同中发起的,存在不同配置服务端则立刻返回,没有则服务端会保持长连接延迟执行任务(30s左右),这中间服务端一旦有配置变更(LocalDataChangeEvent事件)则会提前响应返回
  • 长轮询在获取到不同的配置后还会遍历这些配置主动拉取一次获取具体配置内容并写入本地缓存文件中
  • 集群模式下,配置怎么共享?集群下服务端就必须用数据库来存储配置文件了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/5680.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【U8+】用友U8同一个账套使用了好多年,需要将以前年度进行分离、删除。

【问题需求】 用友U8一个账套使用了好多年了&#xff0c; 需要将以前年度进行删除&#xff0c; 但是在系统管理中&#xff0c;查看该账套只有一个年度账&#xff08;账套库&#xff09;。 例如&#xff1a; 图中002账套&#xff0c;是从2020年开始使用至2024年&#xff0c; 现在…

SpringBoot集成Spring Security——【认证流程】

一、认证流程 上图是 Spring Security 认证流程的一部分&#xff0c;下面的讲解以上图为依据。 &#xff08;1&#xff09; 用户发起表单登录请求后&#xff0c;首先进入 UsernamePasswordAuthenticationFilter&#xff1a; 在 UsernamePasswordAuthenticationFilter中根据用户…

第十四届蓝桥杯模拟赛第一期试题【Java解析】

目录 A 二进制位数 问题描述 答案提交 参考答案 解析 B 晨跑 问题描述 答案提交 参考答案 解析 C 调和级数 问题描述 答案提交 参考答案 解析 D 山谷 问题描述 答案提交 参考答案 解析 E 最小矩阵 问题描述 答案提交 答案 解析 F 核酸日期 问题描述…

香港和新加坡,谁是亚洲加密金融中心?

去新加坡&#xff0c;还是去中国香港? 对中国Web3的创业者来说&#xff0c;是一个问题。 2022年11月&#xff0c;中国香港金融科技周和新加坡金融科技周同时举办&#xff0c;将这场竞赛推向高潮。 抢人、抢钱、抢公司……中国香港和新加坡对于“加密金融中心”或者“全球We…

2、云原生微服务实践-服务开发框架设计和实践

目录 一、依赖管理 二、服务模块管理 api、svc 三、其他文件管理 1、私密配置文件 2、前端页面单页文件 四、单体仓库 mono-repo 1、单体仓库和多仓库的对比&#xff1a; 2、单体仓库优点 五、接口参数校验 六、统一异常处理 七、DTO(数据传输对象)和DMO(数据模型对…

尝试改善科研V2

参考链接&#xff1a; https://fulequn.github.io/2022/09/26/Article202209261/ https://www.xljsci.com/ https://apps.ankiweb.net/ https://www.explainpaper.com/ 1 从动机上促成科研 将科研这件事情分成准备工作、活动本身、活动的结果。 1.1 准备工作 准备工作十分简…

湖南郴州王瑞平、大衣哥、谷传民、孟文豪唱响《知心世界》主题曲

在大衣哥和谷传民对簿公堂之时&#xff0c;来自湖南郴州的王瑞平&#xff0c;准备在他们之间架起一座沟通的桥梁。众所周知&#xff0c;大衣哥和谷传民的官司&#xff0c;是因为《火火的情怀》版权&#xff0c;其实这首歌的版权&#xff0c;并不是只属于谷传民一人。 著名音乐人…

第一个springBoot maven 项目

1. env: java 11 IntelliJ IDEA 2021.3.2 (Community Edition) 2. file->new project->Maven: pom.xml 需要导入的包&#xff1a;后面三个是jdk8升级到11后&#xff0c;可能会出错&#xff0c;需要用到的包 <parent><groupId>org.springframework.boot<…

python面向对象之类和对象相关知识

python面向对象之类和对象相关知识 一、面向对象简介 1、什么是面向对象 面向对象是一种编程思想&#xff0c;把数据和对数据的多个操作方法封装在一起组成类&#xff0c;这样通过这个类创建出来的对象,就可以直接调用这些方法了。2、面向对象相关的术语 类&#xff1a;用来…

一次金融APP的解密历程

声明&#xff1a;本文仅限于技术讨论与分享&#xff0c;严禁用于非法途径。若读者因此作出任何危害网络安全行为后果自负&#xff0c;与本号及原作者无关。 前言&#xff1a; 客户仅提供官网下载地址给我们测试。但是由于官网的版本不是最新的&#xff0c;APP会强制你升级。而…

搭建lamp平台

apache安装步骤 检查是否已经rpm安装httpd服务&#xff0c;已安装则卸载服务。 [rootlocalhost ~]# rpm -e rpm -qa | grep httpd --nodeps 开发工具安装 如果编译安装无法执行&#xff0c;可能是开发工具没有安装&#xff0c;执行下面命令即可安装。&#xff08;如已安装则跳…

【springboot进阶】优雅使用 MapStruct 进行类复制

项目中经常会遇到这样的一个情况&#xff1a;从数据库读取到数据&#xff0c;并不是直接返回给前端做展示的&#xff0c;还需要字段的加工&#xff0c;例如记录的时间戳是不需要的、一些敏感数据更是不能等等。传统的做法就是创建一个新的类&#xff0c;然后写一堆的get/set方法…

数据结构【队列】

文章目录&#xff08;一&#xff09;队列定义&#xff08;二&#xff09;队列实现&#xff08;1&#xff09;创建结构体&#xff08;2&#xff09;具体函数实现及解析1.1 初始化队列1.2入队列1.3出队列1.4取队首元素1.5取队尾元素1.6返回队列个数1.7判断是否为空1.8销毁队列&am…

springCloud的 consul的下载与安装

下载地址&#xff1a;Install | Consul | HashiCorp Developer 下载自己需要使用的版本 下载后会有一个exe 文件通过cmd 命令行来执行这个exe 文件consul agent -dev -client0.0.0.0 出现此页面后执行8500 端口 请求地址&#xff1a;http://127.0.0.1:8500/ 出现此页面说明启…

黑苹果入门:必备工具篇

以下给大家汇总的这些软件工具都是我们在安装使用黑苹果过程中可能会用到的&#xff0c;至于使用方法&#xff0c;在这里我就不做过多介绍了。 本次只提供软件下载地址&#xff0c;不提供使用方法&#xff0c;不知道如何使用软件工具的童鞋&#xff0c;可以在百度翻翻相关教程…

第5章 C语言高级的库函数

文章目录文档配套视频讲解链接地址第05章 C库函数5.1 assert.h 断言库5.2 ctype.h 测试和映射字符5.3 math.h 数学库5.4 stdlib.h 标准库1. 字符串转整数、浮点数2. strtod 把字符串中的数字转换成浮点数并返回数字的下一个字符的位置3. strtol 字符串转整数4. strtoul 字符串转…

vue3 antd多级动态菜单(二)后台管理系统(两种方法过滤有无子菜单children)

vue3 antd 多级动态菜单&#xff08;精修版本&#xff09; 两种方法实现对children的筛选相关文章推送&#xff08;供参考&#xff09;场景复现实现效果解决方法hasChildren与noChilren函数过滤v-if v-else判断有无children【推荐】&#x1f525;两种方法公用代码sunmmary下期预…

ESP32 入门笔记06: WIFI时钟 + FreeRTOS+《两只老虎》 (ESP32 for Arduino IDE)

ESP32FreeRTOS Esp32 模块中已经提供了 FreeRTOS&#xff08;实时操作系统&#xff09;固件。 FreeRTOS有助于提高系统性能和管理模块的资源。FreeRTOS允许用户处理多项任务&#xff0c;如测量传感器读数&#xff0c;发出网络请求&#xff0c;控制电机速度等&#xff0c;所有…

靶向肿瘤代谢,助力攻克癌症

肿瘤代谢的简介 肿瘤代谢的起源在于奥托沃伯格 (Otto Warburg) 的假设&#xff0c;他也因发现线粒体呼吸链复合物 IV 而获得 1931 年诺贝尔生理学或医学奖。Warburg 观察到&#xff0c;与正常组织相比&#xff0c;体外癌组织切片使用大量葡萄糖生成乳酸 (即使在有氧的情况下也是…

SBT10100VDC-ASEMI低压降贴片肖特基二极管SBT10100VDC

编辑-Z SBT10100VDC在TO-263封装里采用的2个芯片&#xff0c;其尺寸都是62MIL&#xff0c;是一款低压降贴片肖特基二极管。SBT10100VDC的浪涌电流Ifsm为150A&#xff0c;漏电流(Ir)为4uA&#xff0c;其工作时耐温度范围为-65~150摄氏度。SBT10100VDC采用金属硅芯片材质&#x…