Nacos 配置中心使用详解

812 阅读9分钟

1. Alibaba Nacos 配置中心简介

Nacos 支持基于 DNS 和基于 RPC 的服务发现(可以作为springcloud的注册中心)、动态配置服务(可以做配置中心)、动态 DNS 服务。

2. Nacos 配置中心使用介绍

2.1. 基础使用

1. 客户端依赖

  1. parent.xml - 采用公共版本依赖管理的方式

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Finchley.SR4</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>
    
    <dependencyManagement>
       <dependencies>
    
           <!--spring cloud alibaba-->
              <dependency>
                  <groupId>com.alibaba.cloud</groupId>
                  <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                  <version>2.0.2.RELEASE</version>
                  <type>pom</type>
                  <scope>import</scope>
              </dependency>
    
       <dependencies>
    
    <dependencyManagement>
    
    1. my-demo.xml

       <!--配置中心客户端-->
       <dependency>
           <groupId>com.alibaba.cloud</groupId>
           <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
       </dependency>
      
    1. 首先必须配置下面这两个配置项
    spring.application.name=nacos-demo
    # spring.cloud.nacos.config.server-addr :Nacos 服务器的地址和端口
    spring.cloud.nacos.config.server-addr=127.0.0.1:8848
    
    • server-addr 属性

      • 代码位置: com.alibaba.nacos.client.config.impl.ServerListManager#ServerListManager(java.util.Properties)

        if (StringUtils.isNotEmpty(serverAddrsStr)) {
                isFixed = true;
                List<String> serverAddrs = new ArrayList<String>();
                String[] serverAddrsArr = serverAddrsStr.split(",");
                for (String serverAddr: serverAddrsArr) {
                    if (serverAddr.startsWith(HTTPS) || serverAddr.startsWith(HTTP)) {
                        serverAddrs.add(serverAddr);
                    } else {
                        String[] serverAddrArr = serverAddr.split(":");
                        if (serverAddrArr.length == 1) {
                            serverAddrs.add(HTTP + serverAddrArr[0] + ":" + ParamUtil.getDefaultServerPort());
                        } else {
                            serverAddrs.add(HTTP + serverAddr);
                        }
                    }
                }
                serverUrls = serverAddrs;
                if (StringUtils.isBlank(namespace)) {
                    name = FIXED_NAME + "-" + getFixedNameSuffix(serverUrls.toArray(new String[serverUrls.size()]));
                } else {
                    this.namespace = namespace;
                    this.tenant = namespace;
                    name = FIXED_NAME + "-" + getFixedNameSuffix(serverUrls.toArray(new String[serverUrls.size()])) + "-"
                        + namespace;
                }
            }
        
      • 从源码可知

        • 实际上是支持配置多个地址的,只需要用 ; 分割。 不具有客户端负载均衡的效果,但是有高可用的效果。

        • 如果配置的某个地址没有带上 ":port" 部分,那么默认会把 8848 作为端口号来拼接。因此在你搭建了NacosVip的情况下,建议让Nginx也监听 8848 端口,或者在你的vip后面补充一个 :80 即可。

    • 说明:之所以需要配置 spring.application.name ,是因为它是构成 Nacos 配置管理 dataId字段的一部分。

      • 在 Nacos Spring Cloud 中,dataId 的完整格式如下:

        prefix{prefix}-{spring.profile.active}.${file-extension}

        • prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix 来配置。

        • spring.profile.active 即为当前环境对应的 profile,详情可以参考 Spring Boot文档。 注意:当 spring.profile.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 prefix.{prefix}.{file-extension}

        • file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 properties 和 yaml 类型。

      • 以上面的例两行配置为例: 此时将会加载 nacos 中 默认(public) 命名空间下的, DEFAULT_GROUP 分组下的 DataID 为 nacos-demo.properties 文件。

      • 如果需要加载指定命名空间(sms-dev)下非默认分组(MY_GROUP1)下的DataID 为 sms-api-service-dev.yaml 的配置文件

        1. bootstrap.yml

          spring:
            application:
              name: @artifactId@
            cloud:
              nacos:
                discovery:
                  server-addr: 127.0.0.1:8848
                config:
                  server-addr: ${spring.cloud.nacos.discovery.server-addr}
                  file-extension: yaml
                  namespace: sms-dev
                  group: MY_GROUP1
            profiles:
              active: @profiles.active@
          
        2. pom.xml 中配置的 artifactId 为 sms-api-service, rofiles.active 为 dev

          • 当然也可以不引用 pom.xml 中的配置。直接在 bootstrap.yml 中写入这两个值。
    • 注意

      当你使用域名的方式来访问 Nacos 时,spring.cloud.nacos.config.server-addr 配置的方式为 域名:port。 例如 Nacos 的域名为abc.com.nacos,监听的端口为 80,则 spring.cloud.nacos.config.server-addr=abc.com.nacos:80。 注意 80 端口不能省略。

    • 示例: 在SpringBoot中打印配置

      public static void main(String[] args) {
            ConfigurableApplicationContext applicationContext = SpringApplication.run(ProviderApplication.class, args);
            String userName = applicationContext.getEnvironment().getProperty("user.name");
            String userAge = applicationContext.getEnvironment().getProperty("user.age");
            System.err.println("user name :"+userName+"; age: "+userAge);
        }
      

    2.2 Nacos 默认支持动态配置更新功能

    1. 当客户端程序加载了NacosServer中的某个配置文件后,如果对该配置文件的内容作了修改,其实在客户端的日志中就能看到通知。

      2020-08-22 14:54:48.620 INFO 6894 --- [-127.0.0.1_8848] c.a.n.client.config.impl.ClientWorker : [fixed-127.0.0.1_8848] [polling-resp] config changed. dataId=api-service-dev.yaml, group=DEFAULT_GROUP 2020-08-22 14:54:48.620 INFO 6894 --- [-127.0.0.1_8848] c.a.n.client.config.impl.ClientWorker : get changedGroupKeys:[sms-api-service-dev.yaml+DEFAULT_GROUP] 2020-08-22 14:54:48.623 INFO 6894 --- [-127.0.0.1_8848] c.a.n.client.config.impl.ClientWorker : [fixed-127.0.0.1_8848] [data-received] dataId=api-service-dev.yaml, group=DEFAULT_GROUP, tenant=null, md5=0e909b0111610cbb22bd3f2e74ecc5e2, content=user.name: nacos-config-yml user.age: 73

      spring: datasource: type: com.alibaba.druid.pool.Dru..., type=yaml

      。。。 2020-08-22 14:54:48.999 INFO 6894 --- [-127.0.0.1_8848] o.s.c.e.event.RefreshEventListener : Refresh keys changed: [user.age] 2020-08-22 14:54:48.999 INFO 6894 --- [-127.0.0.1_8848] c.a.nacos.client.config.impl.CacheData : [fixed-127.0.0.1_8848] [notify-ok] dataId=api-service-dev.yaml, group=DEFAULT_GROUP, md5=0e909b0111610cbb22bd3f2e74ecc5e2, listener=com.alibaba.cloud.nacos.refresh.NacosContextRefresher1@34d07a3e2020082214:54:48.999INFO6894[127.0.0.18848]c.a.nacos.client.config.impl.CacheData:[fixed127.0.0.18848][notifylistener]timecost=376msinClientWorker,dataId=apiservicedev.yaml,group=DEFAULTGROUP,md5=0e909b0111610cbb22bd3f2e74ecc5e2,listener=com.alibaba.cloud.nacos.refresh.NacosContextRefresher1@34d07a3e 2020-08-22 14:54:48.999 INFO 6894 --- [-127.0.0.1_8848] c.a.nacos.client.config.impl.CacheData : [fixed-127.0.0.1_8848] [notify-listener] time cost=376ms in ClientWorker, dataId=api-service-dev.yaml, group=DEFAULT_GROUP, md5=0e909b0111610cbb22bd3f2e74ecc5e2, listener=com.alibaba.cloud.nacos.refresh.NacosContextRefresher1@34d07a3e

    2. 可以使用 @RefreshScope 来使用动态刷新的配置项

      • 示例一:

        import org.springframework.beans.factory.annotation.Value;
        import org.springframework.cloud.context.config.annotation.RefreshScope;
        import org.springframework.web.bind.annotation.RequestMapping;
        import org.springframework.web.bind.annotation.RestController;
        
        @RestController
        @RequestMapping("/config")
        @RefreshScope
        public class TestController {
        
            @Value("${user.age:ttt}")
            private String testValue;
        
            @RequestMapping("/get")
            public String get() {
                System.out.println("testValue======"+testValue);
                return testValue;
            }
        }
        
        • 此时如果在 Nacos 服务器上修改了对应的配置文件的 user.age 配置项的值,那么变量值 testValue 就会自动刷新。
      • 示例二:

        • FilterIgnorePropertiesConfig

          @Data
          @Configuration
          @RefreshScope
          @ConditionalOnExpression("!'${ignore}'.isEmpty()")
          @ConfigurationProperties(prefix = "ignore")
          public class FilterIgnorePropertiesConfig {
          	/**
          	 * 放行url,放行的url不再被安全框架拦截
          	 */
          	private List<String> urls = new ArrayList<>();
          }
          
        • application.yaml

          # 直接放行URL
          ignore:
            urls:
              - /actuator/
              - /auth/login
          
        • 此时在Nacos控制台,修改 application.yaml 中对应的配置项后,点击发布按钮,客户端本地的配置就会被刷新。

    • Tips: 你可以通过配置 spring.cloud.nacos.config.refresh.enabled=false 来关闭动态刷新

    2.3 可支持profile粒度的配置

    github.com/alibaba/spr…

    2.4 支持自定义 namespace 的配置

    github.com/alibaba/spr…

    用于进行租户粒度的配置隔离。不同的命名空间下,可以存在相同的 Group 或 Data ID 的配置。Namespace 的常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。

    • 在没有明确指定 ${spring.cloud.nacos.config.namespace} 配置的情况下, 默认使用的是 Nacos 上 Public 这个namespae。如果需要使用自定义的命名空间,可以通过以下配置来实现:

      spring.cloud.nacos.config.namespace=b3404bc0-d7dc-4855-b519-570ed34b62d7

    • Note

      该配置必须放在 bootstrap.properties 文件中。此外 spring.cloud.nacos.config.namespace 的值是 namespace 对应的 id,id 值可以在 Nacos 的控制台获取。并且在添加配置时注意不要选择其他的 namespae,否则将会导致读取不到正确的配置。

    2.5 支持自定义 Group 的配置

    github.com/alibaba/spr…

    • 在没有明确指定 ${spring.cloud.nacos.config.group} 配置的情况下, 默认使用的是 DEFAULT_GROUP 。如果需要自定义自己的 Group,可以通过以下配置来实现:

      spring.cloud.nacos.config.group=DEVELOP_GROUP

    • Note 该配置必须放在 bootstrap.properties 文件中。并且在添加配置时 Group 的值一定要和 spring.cloud.nacos.config.group 的配置值一致。

    2.6 支持自定义扩展的 Data Id 配置

    github.com/alibaba/spr…

  • Spring Cloud Alibaba Nacos Config 从 0.2.1 版本后,可支持自定义 Data Id 的配置。关于这部分详细的设计可参考 这里。 一个完整的配置案例如下所示:

    spring.application.name=opensource-service-provider
    spring.cloud.nacos.config.server-addr=127.0.0.1:8848
    
    # config external configuration
    # 1、Data Id 在默认的组 DEFAULT_GROUP,不支持配置的动态刷新
    spring.cloud.nacos.config.extension-configs[0].data-id=ext-config-common01.properties
    
    # 2、Data Id 不在默认的组,不支持动态刷新
    spring.cloud.nacos.config.extension-configs[1].data-id=ext-config-common02.properties
    spring.cloud.nacos.config.extension-configs[1].group=GLOBALE_GROUP
    
    # 3、Data Id 既不在默认的组,也支持动态刷新
    spring.cloud.nacos.config.extension-configs[2].data-id=ext-config-common03.properties
    spring.cloud.nacos.config.extension-configs[2].group=REFRESH_GROUP
    spring.cloud.nacos.config.extension-configs[2].refresh=true
    
  • 可以看到:

    • 通过 spring.cloud.nacos.config.extension-configs[n].data-id 的配置方式来支持多个 Data Id 的配置。

    • 通过 spring.cloud.nacos.config.extension-configs[n].group 的配置方式自定义 Data Id 所在的组,不明确配置的话,默认是 DEFAULT_GROUP。

    • 通过 spring.cloud.nacos.config.extension-configs[n].refresh 的配置方式来控制该 Data Id 在配置变更时,是否支持应用中可动态刷新, 感知到最新的配置值。默认是不支持的。

    • Note

      • 多个 Data Id 同时配置时,他的优先级关系是 spring.cloud.nacos.config.extension-configs[n].data-id 其中 n 的值越大,优先级越高。
    • Note

      • spring.cloud.nacos.config.extension-configs[n].data-id 的值必须带文件扩展名,文件扩展名既可支持 properties,又可以支持 yaml/yml。 此时 spring.cloud.nacos.config.file-extension 的配置对自定义扩展配置的 Data Id 文件扩展名没有影响。

      • 通过自定义扩展的 Data Id 配置,既可以解决多个应用间配置共享的问题,又可以支持一个应用有多个配置文件。

2.7. 支持多个共享 Data Id 的配置

  • 一般来说,使用配置中心都会提取一些公共的环境变量放在单独的一个配置文件里面,比如说把数据库url用户名密码什么之类的放到一个配置文件,所有的应用公用这个配置。

  • 为了更加清晰的在多个应用间配置共享的 Data Id ,你可以通过以下的方式来配置:

    # 配置支持共享的 Data Id
    spring.cloud.nacos.config.shared-configs[0].data-id=common.yaml
    
    # 配置 Data Id 所在分组,缺省默认 DEFAULT_GROUP
    spring.cloud.nacos.config.shared-configs[0].group=GROUP_APP1
    
    # 配置Data Id 在配置变更时,是否动态刷新,缺省默认 false
    spring.cloud.nacos.config.shared-configs[0].refresh=true
    
  • 可以看到:

    通过 spring.cloud.nacos.config.shared-configs[n].data-id 来支持多个共享 Data Id 的配置。

    通过 spring.cloud.nacos.config.shared-configs[n].group 来配置自定义 Data Id 所在的组,不明确配置的话,默认是 DEFAULT_GROUP。

    通过 spring.cloud.nacos.config.shared-configs[n].refresh 来控制该Data Id在配置变更时,是否支持应用中动态刷新,默认false。

  • 示例:

    1. nacos Server 中添加一个 application-dev.yaml 作为公共共享配置文件

    2. bootstrap.yml

      spring:
        application:
          name: @artifactId@
        cloud:
          nacos:
            discovery:
              server-addr: 127.0.0.1:8848
            config:
              server-addr: ${spring.cloud.nacos.discovery.server-addr}
              file-extension: yaml
              namespace: sms-dev
              group: DEFAULT_GROUP
              shared-configs:
                - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
        profiles:
          active: @profiles.active@
      
      • shared-configs 也支持配置 group 与 refresh 属性

        spring:
          application:
            name: @artifactId@
          cloud:
            nacos:
              discovery:
                server-addr: 127.0.0.1:8848
              config:
                server-addr: ${spring.cloud.nacos.discovery.server-addr}
                file-extension: yaml
                namespace: sms-dev
                group: DEFAULT_GROUP
                shared-configs:
                  - data-id: application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
                    group: DEFAULT_GROUP
                    refresh: false
          profiles:
            active: @profiles.active@
        
    3. 设置pom.xml 中配置的 artifactId 为 sms-api-service, rofiles.active 为 dev。

    4. 启动后,程序将将会加载 application-dev.yaml 还有 sms-api-service-dev.yaml

      • Tips: 客户端依赖的版本如果是 2.0.1.RELEASE 那么应该用 shared-dataids 配置

      • 例:

        1. parent.xml

            ```xml
            <dependencyManagement>
            <dependencies>
          
            <!--spring cloud alibaba-->
            <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2.0.1.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
            </dependency>
          
            <dependencies>
          
            <dependencyManagement>
            ```
          
         2. bootstrap.properties
        
                * 例一:
        
                ```
                # 配置内容为字符串类型,并且多个配置可以用 `,` 分割。
                spring.cloud.nacos.config.shared-dataids = application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
                ```
        
                * 例二:
        
                ```
                # 共享配置
                spring.cloud.nacos.config.shared-dataids=common-log.yaml,common-redis.yaml
                spring.cloud.nacos.config.refreshable-dataids=common-log.yaml
                ```
        
                * 通过spring.cloud.nacos.config.refreshable-dataids
        
                	来支持哪些共享配置的Data Id在变化时,应用中是否动态刷新,能感知到最新的值,多个DataId之间用逗号隔开。如果没有明确指定的配置,默认情况下所共享的配置是不支持动态刷新。
        
         * 目前 2.0.2.RELEASE 开始就推荐改用 spring.cloud.nacos.config.shared-configs 来代替了。		
        
        
        
        
        
        
        
        
        
        
           		
        

    2.8. 配置文件的优先级

    • Spring Cloud Alibaba Nacos Config 目前提供了三种配置能力从 Nacos 拉取相关的配置。

        A: 通过 spring.cloud.nacos.config.shared-configs[n].data-id 支持多个共享 Data Id 的配置
      
        B: 通过 spring.cloud.nacos.config.extension-configs[n].data-id 的方式支持多个扩展 Data Id 的配置
      
        C: 通过内部相关规则(应用名、应用名+ Profile )自动生成相关的 Data Id 配置
      
        当三种方式共同使用时,他们的一个优先级关系是:A < B < C
      
      • 加载配置的顺序,公共配置 -> 扩展配置 -> 私有配置,如果有相同key的后面的覆盖前面的

      NacosPropertySourceRepository

    2.9. 完全关闭配置

      通过设置 spring.cloud.nacos.config.enabled = false 来完全关闭 Spring Cloud Nacos Config
    

3. Nacos 配置中心原理

  • 推还是拉?

    • 其实客户端和服务端之间的数据交互,无外乎两种情况:

      服务端推数据给客户端 客户端从服务端拉数据

    • 那到底是推还是拉呢,从 Nacos 客户端通过 Listener 来接收最新数据的这个做法来看,感觉像是服务端推的数据?

      • 答案:

        • Nacos 并不是通过推的方式将服务端最新的配置信息发送给客户端的,而是客户端维护了一个长轮询的任务,定时去拉取发生变更的配置信息,然后将最新的数据推送给 Listener 的持有者。

        • Tips: Nacos 的客户端与服务端 与 RocketMQ 中的 consumer 与 Broker 的通信机制一样,都是采用长轮询。

      • 拉的优势 如果用推的方式,服务端需要维持与客户端的长连接,这样的话需要耗费大量的资源,并且还需要考虑连接的有效性,例如需要通过心跳来维持两者之间的连接。而用拉的方式,客户端只需要通过一个无状态的 http 请求即可获取到服务端的数据。

  • 配置快照(本地)

    Nacos 的客户端 SDK 会在本地生成配置的快照。当客户端无法连接到 Nacos Server 时,可以使用配置快照显示系统的整体容灾能力。

    • 快照位置示例: 2020-11-04 18:59:41.606 INFO 67478 --- [ main] c.a.n.c.c.impl.LocalConfigInfoProcessor 195 : LOCAL_SNAPSHOT_PATH:/Users/*/nacos/config

      • failover -> openApi -> snapshot.

        failover是用户自己写入的,获取完openApi会把数据写到snapshot。

        首先获取的failover是用户用于容灾的救急设置(比如nacos连接不上获取不到新配置),需要用户自己写入创建并写入该文件。当有该文件时优先使用。

        一般情况下不会用到failover。 通过openAPI从nacos获取,然后将这份配置持久化到本地快照备查,快照文件不是容灾文件,所以写入的文件和启动初始查询的文件不同。

      • 用户通过客户端 SDK 访问服务端的配置,同时建立 HTTP 的长轮询监听配置项变更,同时为了减轻服务端压力和保证容灾特性,配置项拉取到客户端之后会保存一份快照在本地文件中,SDK 优先读取文件里的内容。

  • 配置动态刷新原理

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

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

    考虑到服务端故障的问题,客户端将最新数据获取后会保存在本地的 snapshot 文件中,以后会优先从文件中获取配置信息的值。