基本概念

配置服务:在服务或者应用运行过程中,提供动态配置或者元数据以及配置管理的服务提供者。

配置项:⼀个具体的可配置的参数与其值域,通常以 param-key = param-value 的形式存在。例如我们常 配置系统的日志输出级别(logLevel = INFO | WARN | ERROR) 就是⼀个配置项。

配置集:⼀组相关或者不相关的配置项的集合称为配置集。在系统中,⼀个配置文件通常就是⼀个配置集, 包含了系统各个方面的配置。例如,⼀个配置集可能包含了数据源、线程池、日志级别等配置项。

命名空间(Namespace):用于进行租户粒度的配置隔离。不同的命名空间下,可以存在相同的 Group 或 Data ID 的配置。 Namespace 的常用场景之⼀是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源 (如数据库配置、限流阈值、降级开关)隔离等。如果在没有指定 Namespace 的情况下,默认使 用 public 命名空间。

配置组(Group):Nacos 中的⼀组配置集,是配置的维度之⼀。通过⼀个有意义的字符串(如 ABTest 中的实验组、 对照组)对配置集进行分组,从而区分 Data ID 相同的配置集。当您在 Nacos 上创建⼀个配置时, 如果未填写配置分组的名称,则配置分组的名称默认采用 DEFAULT_GROUP 。配置分组的常见场景:不同的应用或组件使用了相同的配置项,如 database_url 配置和 MQ_Topic 配置。

配置 ID(Data ID):Nacos 中的某个配置集的 ID。配置集 ID 是划分配置的维度之⼀。Data ID 通常用于划分系统的配 置集。⼀个系统或者应用可以包含多个配置集,每个配置集都可以被⼀个有意义的名称标识。Data ID 尽量保障全局唯⼀,可以参考 Nacos Spring Cloud 中的命名规则:${prefix}-${spring.profiles.active}.${file-extension}

配置快照(Configuration Snapshot):Nacos 的客户端 SDK 会在本地生成配置的快照。当客户端无法连接到 Nacos Server 时,可以使 用配置快照显示系统的整体容灾能力。配置快照类似于 Git 中的本地 commit,也类似于缓存,会 在适当的时机更新,但是并没有缓存过期(expiration)的概念。

nacos配置mongodb nacos配置中心原理_nacos配置mongodb

nacos配置mongodb nacos配置中心原理_开发语言_02

 Nacos配置模型:

nacos配置mongodb nacos配置中心原理_开发语言_03

 

  1. Nacos 提供可视化的控制台,可以对配置进行发布、更新、删除、灰度、版本管理等功能。
  2. SDK 可以提供发布配置、更新配置、监听配置等功能。
  3. 2.0之前,SDK通过http长轮询监听配置变更,Server端对比Client 端配置的MD5和本地MD5是否相等,不相等推送配置变更。
  4. SDK 会保存配置的快照,当服务端出现问题的时候从本地获取

配置资源模型

Namespace 的设计就是用来进行资源隔离的,我们在进行配置资源的时候可以从以下两个角度来 看:从单个租户的角度来看,我们要配置多套环境的配置,可以根据不同的环境来创建 Namespace 。 比如开发环境、测试环境、线上环境,我们就创建对应的 Namespace(dev、test、prod), Nacos 会自动生成对应的 Namespace Id 。如果同⼀个环境内想配置相同的配置,可以通过 Group 来区分。如下图所示

nacos配置mongodb nacos配置中心原理_nacos配置mongodb_04

从多个租户的角度来看,每个租户都可以有自己的命名空间。我们可以为每个用户创建⼀个命名空 间,并给用户分配对应的权限,比如多个租户(zhangsan、lisi、wangwu),每个租户都想有⼀套 自己的多环境配置,也就是每个租户都想配置多套环境。那么可以给每个租户创建⼀个 Namespac e (zhangsan、lisi、wangwu)。同样会生成对应的 Namespace Id。然后使用 Group 来区分不 同环境的配置。如下图所示

nacos配置mongodb nacos配置中心原理_java_05

下面一起来了解下 Nacos 的动态配置的能力,看看 Nacos 是如何以简单、优雅、高效的方式管理配置,实现配置的动态变更的。本文根据nacos 1.4.1版本进行分析

新建配置:

后台启动nacos服务,在控制台创建一个简单的配置集,配置集有一个配置项 age=18

nacos配置mongodb nacos配置中心原理_开发语言_06

 

nacos配置mongodb nacos配置中心原理_客户端_07

首先通过工厂类,根据服务地址创建一个configService配置服务的对象
ConfigService configService = NacosFactory.createConfigService(properties);

通过这个对象的getConfig方法获取相应dataId和group下的配置内容,除了获取配置
还可进行增删改等操作
String content = configService.getConfig(dataId, group, 5000);

//为这个dataId+group的配置添加一个监听器
configService.addListener(dataId,group,new Listener(){...})

当创建配置服务的时候,客户端会读取配置信息,放到本地缓存中,当我们修改配置的时候,

nacos配置mongodb nacos配置中心原理_开发语言_08

 

nacos配置mongodb nacos配置中心原理_nacos配置mongodb_09

此时,通过回调方法拿到服务端配置的最新数据

configService.addListener(dataId, group, new Listener() {
            @Override
            public void receiveConfigInfo(String configInfo) {
                System.out.println("receive:" + configInfo);
            }
        });

 到此,一个简单的动态配置管理功能就完成了,删除和新增配置和他类似,就不再赘述

Nacos客户端如何获取到服务端最新的配置数据?

客户端和服务端通信有两种方式

  • 客户端去服务端拉取数据
  • 服务端向客户端推送数据

从上面启动客户端代码来看,配置服务注册了一个Linstener监听器,通过回调方法接受服务端最新数据来看,猜测nacos应该采用的是服务端向客户端推送最新数据,带着这个猜测去源码中寻找答案

创建configService

首先通过工厂类,根据服务地址创建一个configService配置服务的对象
ConfigService configService = NacosFactory.createConfigService(properties);

nacos配置mongodb nacos配置中心原理_开发语言_10

  1. 通过反射,获取NacosConfigService类对象
  2. 拿到有参构造器
  3. 根据参数创建NacosConfigService对象

进入这个有参构造,看看在创建对象时做了哪些事情

nacos配置mongodb nacos配置中心原理_服务端_11

由上图可知,在构造方法里初始化了两个对象

  • HttpAgent:具体业务处理是ServerHttpAgent类来实现,通过注释可知是处理http请求。通过其提供的方法可知,对外提供get、post、delete 请求

nacos配置mongodb nacos配置中心原理_java_12

  •  ClientWorker:通过注释可知这个worker是长轮询的实现

nacos配置mongodb nacos配置中心原理_nacos配置mongodb_13

可以看到 ClientWorker 除了将HttpAgent 维持在自己内部,还初始化了两个线程池

每隔10ms执行一次checkConfigInfo()方法,只有在这个方法执行结束后隔10ms再次执行。
        
this.executor.scheduleWithFixedDelay(new Runnable() {
    @Override
    public void run() {
        try {
            checkConfigInfo();
        } catch (Throwable e) {
            LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);
        }
    }
}, 1L, 10L, TimeUnit.MILLISECONDS);
初始化线程池,核心线程个数为操作系统核心数,具体在checkConfigInfo方法中向这个线程池添加任务
主要功能是执行与服务端的长轮训通讯

this.executorService = Executors
        .newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("com.alibaba.nacos.client.Worker.longPolling." + agent.getName());
                t.setDaemon(true);
                return t;
            }
        });

每隔10ms都要执行checkConfigInfo方法,接下来看看checkConfigInfo方法做了什么

nacos配置mongodb nacos配置中心原理_开发语言_14

cacheMap:缓存配置集的map,key是dataId+group , value是 cacheData 对象 

通过代码可知,从缓存中取出配置集数量,除以 3000 后向上取整做为要执行的一批任务。也就是3000个配置集做为1次检查的最大数量。然后创建一个LongPollingRunnable()提交给executorService执行。每10ms检查下是否需要创建新的任务。如果配置集不变,则不需要再次创建任务。

接下来看看 LongPollingRunnable() 都做了些什么。主要做了两个任务,一个是检查本地配置,另一个是获取服务端的配置信息更新到本地

nacos配置mongodb nacos配置中心原理_java_15

checkLocalConfig(),通过这个方法发现Nacos将配置信息保存在

~/nacos/config/fixed-{address}_8848_nacos/snapshot/DEFAULT_GROUP/{dataId},如果cacheData中缓存的配置信息的MD5值和listener中的MD5值不一致,则将缓存数据写入linstener中,调用监听器的回调方法通知客户端。

checkUpdateDataIds()方法是从服务端获取哪些值发生变化的dataId+group 的集合,如果集合不为空,说明有配置发生变化,则通过getServerConfig()方法获取最新配置,设置到cacheMap中。

nacos配置mongodb nacos配置中心原理_nacos配置mongodb_16

nacos配置mongodb nacos配置中心原理_服务端_17

有几个重要的方法

从服务端获取哪些配置发生变化,此处是一个Post请求,如果服务端配置没有更新,
则在服务端hang on 30s,如果有配置变化,则马上返回数据。这就是长轮训

List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);


变化的dataId+group做为条件,通过get请求拿到最新配置
String[] ct = getServerConfig(dataId, group, tenant, 3000L);



检查cacheData里的MD5值和cacheData.listener里的MD5值,如果不一致则将listener中的context
和MD5数据更新为cacheData中的最新值。并且调用listener的回调方法通知configService
cacheData.checkListenerMd5();

添加Listener

在创建完成configService后,为这个对象添加一个监听对象

nacos配置mongodb nacos配置中心原理_nacos配置mongodb_18

传入三个参数,dataId、group、和 Listener,看下具体是怎么实现的

nacos配置mongodb nacos配置中心原理_开发语言_19

加载本地的配置,获取配置的contect,计算出MD5值,将其封装到CacheData中
CacheData cache = addCacheDataIfAbsent(dataId, group, tenant);

如何触发回调

nacos配置mongodb nacos配置中心原理_开发语言_20

 

nacos配置mongodb nacos配置中心原理_开发语言_21

至此配置中心的完整流程已经分析完毕了,可以发现,Nacos 并不是通过推的方式将服务端最新的配置信息发送给客户端的,而是客户端维护了一个长轮询的任务,定时去拉取发生变更的配置信息,然后将最新的数据推送给 Listener 的持有者。

总结

nacos配置mongodb nacos配置中心原理_java_22

Nacos 服务端创建了相关的配置项后,客户端就可以进行监听了。

客户端是通过一个定时任务来检查自己监听的配置项的数据的,一旦服务端的数据发生变化时,客户端将会获取到最新的数据,并将最新的数据保存在一个 CacheData 对象中,然后会重新计算 CacheData 的 md5 属性的值,此时就会对该 CacheData 所绑定的 Listener 触发 receiveConfigInfo 回调。

客户端是通过一个定时任务来检查自己监听的配置项的数据的(每10ms检查一次) 检查 CacheData 中的md5 和每个Listener 持有的md5 是否相同,如果不同 说明服务端的配置发生了改变,这个时候我们就得去拉取新的服务端的数据并刷新,这个md5是通过长轮询获取变化的配置时改变的,这个长轮询的超时时间是30s并且在这个方法的最后继续提交长轮询任务,说明这个轮询会一直跑下去,这样每次服务端 有配置变更就可用通知的到客户端了,考虑到服务端故障的问题,客户端将最新数据获取后会保存在本地的 snapshot 文件中,以后会优先从文件中获取配置信息的值