Nacos Config自定义nameSpace和group

    Nacos加载远端配置及实时配置更新 - 图1
    nacos数据模型
    namespace:用于解决多环境及多组环数据的隔离问题,在不同的namespace下可以存在相同的group;
    group:group是nacos中用来实现dataid 分组管理的机制,可以实现不同的service/dataid的隔离;对于group的用法,其实没有固定的规定,比如它可以实现不同 环境下的dataid的分组,也可以实现不同应用或者组件下使用相同配置类型的分组;
    service、dataid:配置名称

    配置的增删改查
    Nacos加载远端配置及实时配置更新 - 图2
    nacos配置中心

    对于nacos Config来说,其实就是提供了配置的集中式管理功能,然后对外提供crud的访问接口是的应用系统可以完成配置的基本操作;
    nacos服务端的数据存储默认采用的是Derby数据库,除此之外还支持mysql数据库;

    配置的实时更新-动态监听
    当nacos Config server上的配置发生变化时,需要让相关的应用程序感知配置的变化,需要客户端针对感兴趣的配置实现监听;
    客户端与服务端之间的数据交互的两种方式
    pull 表示客户端从服务端主动拉取数据
    push 表示服务端主动把数据推送到客户端
    push:服务端需要维持与可短短的长链接,如果客户端的数量比较多,那么服务端需要耗费大量的内存资源来保存每个连接,并且为了检测连接的有效性,还需要心跳机制来维持每个连接的状态;
    pull:客户端需要定时从服务端拉取一次数据,由于定时任务会存在一定的时间间隔,所以不能保证实时性,并且在服务端配置长时间不更新的情况下,客户端的定时任务会做一些无效的pull;
    nacos采用的是pull方式,但是不是一种简单的pull,而是一种长轮询机制,结合push与pull的优势;客户端采用长轮询的方式定时发起pull请求,去检查服务端配置是否发生了变更,如果发生了变更,则客户端会更会变更的数据获得最新的配置;所谓长轮询,是客户端发起轮询请求后,服务端如果有配置方式发生变更,就追返回;
    Nacos加载远端配置及实时配置更新 - 图3
    Nacos Client 发起Pull请求
    服务端如果没有配置发生变更则会Hold住这个请求,直到配置发生变化后或者29.5s后,服务端就会把这个hold住的请求返回;

    Nacos加载远端配置及实时配置更新 - 图4
    nacos的长轮询机制
    Spring Cloud实现配置的加载
    Spring cloud是基于Spring来进行扩展的,Spring原本提供了Environment接口,它抽象了一个Environment来表示Spring应用程序环境配置,整合了各种各样的外部环境,并且提供统一访问的方法getProperty(String key),在Spring启动时,会把配置加载到Environment中,当创建一个Bean时可以从Environment中把一些属性值通过@Value的形式注入业务代码。所以SpringCloud加载nacos配置的本质上是要如何解决如何讲远程配置加载到Environment中以及如何实时更新配置变更到Environment中
    SpringBoot应用启动时调用SpringApplication.run()方法,在run方法中可以看到有一个prepareEnvironment()方法,具体代码如下

    1. public ConfigurableApplicationContext run(String... args) {
    2. //省略部分代码
    3. try {
    4. ApplicationArguments applicationArguments = new DefaultApplicationArguments(
    5. args);
    6. ConfigurableEnvironment environment = prepareEnvironment(listeners,
    7. applicationArguments);
    8. //省略部分代码
    9. listeners.started(context);
    10. callRunners(context, applicationArguments);
    11. }
    12. catch (Throwable ex) {
    13. handleRunFailure(context, ex, exceptionReporters, listeners);
    14. throw new IllegalStateException(ex);
    15. }
    16. try {
    17. listeners.running(context);
    18. }
    19. catch (Throwable ex) {
    20. handleRunFailure(context, ex, exceptionReporters, null);
    21. throw new IllegalStateException(ex);
    22. }
    23. return context;
    24. }

    在prepareEnvironment()方法中会发布一个ApplicationEnvironmentPrepareEvent事件,所有对这个时间感兴趣的Listener都会监听到该事件;

    1. private ConfigurableEnvironment prepareEnvironment(
    2. SpringApplicationRunListeners listeners,
    3. ApplicationArguments applicationArguments) {
    4. // Create and configure the environment
    5. ConfigurableEnvironment environment = getOrCreateEnvironment();
    6. configureEnvironment(environment, applicationArguments.getSourceArgs());
    7. listeners.environmentPrepared(environment);
    8. bindToSpringApplication(environment);
    9. if (!this.isCustomEnvironment) {
    10. environment = new EnvironmentConverter(getClassLoader())
    11. .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
    12. }
    13. ConfigurationPropertySources.attach(environment);
    14. return environment;
    15. }

    其中BootstrapApplicationListener会收到该事件并进行处理

    1. public class BootstrapApplicationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
    2. public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    3. ConfigurableEnvironment environment = event.getEnvironment();
    4. if ((Boolean)environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class, true)) {
    5. if (!environment.getPropertySources().contains("bootstrap")) {
    6. ConfigurableApplicationContext context = null;
    7. String configName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
    8. Iterator var5 = event.getSpringApplication().getInitializers().iterator();
    9. while(var5.hasNext()) {
    10. ApplicationContextInitializer<?> initializer = (ApplicationContextInitializer)var5.next();
    11. if (initializer instanceof ParentContextApplicationContextInitializer) {
    12. context = this.findBootstrapContext((ParentContextApplicationContextInitializer)initializer, configName);
    13. }
    14. }
    15. if (context == null) {
    16. context = this.bootstrapServiceContext(environment, event.getSpringApplication(), configName);
    17. event.getSpringApplication().addListeners(new ApplicationListener[]{new BootstrapApplicationListener.CloseContextOnFailureApplicationListener(context)});
    18. }
    19. this.apply(context, event.getSpringApplication(), environment);
    20. }
    21. }
    22. }
    23. }

    在bootstrapServiceContext方法中

    1. private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvironment environment, final SpringApplication application, String configName) {
    2. //省略部分代码
    3. builder.sources(new Class[]{BootstrapImportSelectorConfiguration.class});
    4. ConfigurableApplicationContext context = builder.run(new String[0]);
    5. context.setId("bootstrap");
    6. this.addAncestorInitializer(application, context);
    7. bootstrapProperties.remove("bootstrap");
    8. this.mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
    9. return context;
    10. }

    可以看到 builder.sources(new Class[]{BootstrapImportSelectorConfiguration.class});方法,查看BootstrapImportSelectorConfiguration类发现在该类上使用了@Import引入了BootstrapImportSelector类,在该类中的selectImport方法又可以看到Spring的SPI机制,可以到classpath路径下查找META-INF/spring.factories预定义的一些扩展点,其中key为BootstrapConfiguration

    1. org.springframework.cloud.bootstrap.BootstrapConfiguration=\
    2. org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
    3. org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
    4. org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
    5. org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration

    至此,准备工作就完成了,
    从新回到run方法中,

    1. public ConfigurableApplicationContext run(String... args) {
    2. ConfigurableEnvironment environment = prepareEnvironment(listeners,
    3. applicationArguments);
    4. prepareContext(context, environment, listeners, applicationArguments,
    5. printedBanner);
    6. return context;
    7. }

    在方法中可以看到prepareContext()方法,该方法进行刷新应用上下文的主备阶段,接着调用applyInitializers()方法

    1. private void prepareContext(ConfigurableApplicationContext context,
    2. ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
    3. ApplicationArguments applicationArguments, Banner printedBanner) {
    4. //省略部分代码
    5. listeners.contextPrepared(context);
    6. //省略部分代码
    7. }

    该方法主要会执行容器中的ApplicationContextInitzalizer,它的作用是在应用程序上下文初始化的时候做一些额外的操作

    1. protected void applyInitializers(ConfigurableApplicationContext context) {
    2. for (ApplicationContextInitializer initializer : getInitializers()) {
    3. Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
    4. initializer.getClass(), ApplicationContextInitializer.class);
    5. Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
    6. initializer.initialize(context);
    7. }
    8. }

    该方法的类为PropertySourceBootstrapConfiguration实现了ApplicationContextInitalizer接口,所以在applyInitizers方法中调用initalizer方法,最终会调用PropertySourceBootstrapConfiguration中的initialize方法

    1. public void initialize(ConfigurableApplicationContext applicationContext) {
    2. PropertySourceLocator locator = (PropertySourceLocator)var5.next();
    3. //省略部分代码
    4. source = locator.locate(environment);
    5. //省略部分代码
    6. }

    PropertySourceLocator接口的主要作用是实现应用外部化配置可动态加载,而NacosProPertySourceLocator实现了了该接口,所以此时locator.locate实际调用的是NacosPropertySourceLocator中的locate方法,在NacosPropertySourceLocator的locate方法中把存放在Nacos Server中的配置信息读取出来,然后把结果存到PropertySource的实例中并返回;在该方法的实现中海油一个longPollingRunable.run方法,LongpollingRunable实际上是一个线程,run方法的作用如下
    通过checkLocalConfig方法来检查本地配置
    执行checkUpdateDataIds方法和在服务端建立长轮询机制,从服务端获取发生变更的数据
    遍历变更数据集合changedGroupKeys,调用getServerConfig方法,根据DataId,group。tenant去服务端读取对应的配置信息并保存到本地文件中。
    总结:

    Nacos加载远端配置及实时配置更新 - 图5