Nacos配置中心

配置中心示意图

当我们把一个单体应用拆分成多个应用时,就会从一个配置文件 拆分成多个配置文件,管理这么多的配置文件,十分复杂和繁琐。

为了优化这个问题,我们可以把多个应用中的配置信息剥离出来,存入 配置中心 统一管理。

1)什么是Nacos 配置中心

Nacos 除了可以做注册中心,还提供了配置中心,可以存储和管理 服务的配置文件以及key-value的其他数据。

配置中心示意图

如图所示,我们可以把微服务的配置文件 存储到配置中心,服务在启动时候会去配置中心中拉去最新的配置文件。

2)Nacos配置中心架构

https://note.youdao.com/yws/public/resource/968f9f6e62248d986dd9ca9c8d9b61cf/xmlnote/9E32BA0E18364AD586B597EFC174C3CE/54895

服务端

1、服务端启动后通过NacosClient去配置中心获取配置数据

2、服务启动后,NacosClient会根据配置文件的key作为ID开启监听器,当监听到 配置中心的配置文件发生变动,会立即拉去最新的配置数据到服务端

用户端

用户可通过管理控制器 去修改配置文件

Nacos服务端

当用户修改配置文件后,节点会把修改的信息同步给集群中的其他节点并存储到持久化层(MySQL数据库)

3)Nacos配置中心核心源码入口点

package com.alibaba.nacos.api.config;

ConfigService

image-20230405142619334

Nacos 核心API实验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class ConfigServerDemo {

public static void main(String[] args) throws NacosException, InterruptedException {
String serverAddr = "localhost";
String dataId = "nacos-config-demo.yaml";
String group = "DEFAULT_GROUP";
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr);
//获取配置服务
ConfigService configService = NacosFactory.createConfigService(properties);
//获取配置
String content = configService.getConfig(dataId, group, 5000);
System.out.println(content);
//注册监听器
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
System.out.println("===recieve:" + configInfo);
}

@Override
public Executor getExecutor() {
return null;
}
});

//发布配置
//boolean isPublishOk = configService.publishConfig(dataId, group, "content");
//System.out.println(isPublishOk);
//发送properties格式
configService.publishConfig(dataId,group,"common.age=30", ConfigType.PROPERTIES.getType());

Thread.sleep(3000);
content = configService.getConfig(dataId, group, 5000);
System.out.println(content);

// boolean isRemoveOk = configService.removeConfig(dataId, group);
// System.out.println(isRemoveOk);
// Thread.sleep(3000);

// content = configService.getConfig(dataId, group, 5000);
// System.out.println(content);
// Thread.sleep(300000);

}
}

image-20230405143731255

Spring Cloud集成Nacos配置中心

1)引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.javaxing</groupId>
<artifactId>NacosConfig</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>NacosConfig</name>
<description>NacosConfig</description>

<properties>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.8.RELEASE</spring-cloud-alibaba.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!-- 引入nacos依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>

<!-- 引入nacos 配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>


</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>



<dependencyManagement>
<dependencies>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

2)配置bootstrap.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
application:
name: nacos-config

cloud:
nacos:
# 配置中心IP
config:
server-addr: 10.211.55.12:8848
# 读取以yaml结尾的配置文件,那就是nacos-config.yaml
file-extension: yaml
# 注册中心IP
discovery:
server-addr: 10.211.55.12:8848

注意:必须使用 bootstrap 配置文件来配置Nacos Server 地址

application.yml作用域在于当前应用有效,bootstrap.yml系统级别的配置有效(一般采用远程配置的时候才会用到)。

3)启动服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.javaxing.nacosconfig;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class NacosConfigApplication {

public static void main(String[] args) throws InterruptedException {
ConfigurableApplicationContext context = SpringApplication.run(NacosConfigApplication.class, args);
while (true){
//当动态配置刷新时,会更新到 Enviroment中,因此这里每隔一秒中从Enviroment中获取配置
System.out.println(context.getEnvironment().getProperty("aaa"));
Thread.sleep(3000);
}
}
}

我们在启动服务时,循环打印出 配置中心的文件参数

image-20230405162954907

启动成功后,会先去拉取 nacos-config 的配置文件,也会拉取nacos-config.yaml (因为我们设置了以结尾yaml的配置文件),所以会优先使用yaml

4)配置文件高级参数

4.1 逻辑隔离

逻辑隔离模式

Nacos配置中心的隔离模型和注册中心一样,分为Namespace 命名空间,Group 分组,Service 应用。

Namespace:公共命名空间 public

不同命名空间的节点是完全隔离开的,相互之间无法访问。一般用于不同环境的隔离,如 开发测试环境(dev)和生产环境(prod)的隔离。

Group:默认分组 DEFAULT_GROUP

不同的服务可以归类到同一个分组中,分组也能起到隔离的作用,不同的分组之间无法相互访问。

4.3 profile 指定多个配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
spring:
application:
name: nacos-config

cloud:
nacos:
# 配置中心IP
config:
server-addr: 10.211.55.12:8848
# 读取以yaml结尾的配置文件,那就是nacos-config.yaml
file-extension: yaml

# 注册中心IP
discovery:
server-addr: 10.211.55.12:8848
# 设置profile,那么实际获取的配置文件就是:nacos-config-dev.yaml
profiles:
active: dev

新建一个nacos-config-dev.yaml

image-20230405170845765

重启服务后,成功获取到了nacos-config-dev.yaml文件的参数

image-20230405170826581

4.4 自定义namespace命名空间

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
application:
name: nacos-config
cloud:
nacos:
# 配置中心IP
config:
server-addr: 10.211.55.12:8848
# 读取以yaml结尾的配置文件,那就是nacos-config.yaml
file-extension: yaml

# 设置命名空间
namespace: 37731914-6c28-4265-a090-7a9391ed843d

如果我们指定了namespace,那么服务只会去对应的namespace中获取的配置文件。

namespace一般用于不同环境的分区隔离,如 dev开发环境、prod生产环境、uat测试环境等。

4.5 自定义Group分组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spring:
application:
name: nacos-config
cloud:
nacos:
# 配置中心IP
config:
server-addr: 10.211.55.12:8848
# 读取以yaml结尾的配置文件,那就是nacos-config.yaml
file-extension: yaml

# 设置命名空间
namespace: 37731914-6c28-4265-a090-7a9391ed843d

# 设置分组
group: DEFAULT_GROUP

默认分组:DEFAULT_GROUP

4.6 读取多个配置文件

Data ID:每一个配置文件的唯一标识

很多时候配置文件的参数是可以共用的,如 redis、mysql、rabbitMQ,没必要在每个配置文件都配上这样的参数。

那么我们就可以在一个微服务上面读取多个配置,也就是可以配置多个Data ID。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
spring:
application:
name: nacos-config

cloud:
nacos:
# 配置中心IP
config:
server-addr: 10.211.55.12:8848
# 读取以yaml结尾的配置文件,那就是nacos-config.yaml
file-extension: yaml
namespace: 37731914-6c28-4265-a090-7a9391ed843d
group: DEFAULT_GROUP



# 共享配置
shared-configs:
- data-id: common-mysql.yaml
group: SPRING_CLOUD_EXAMPLE_GROUP

- data-id: common-redis.yaml
group: SPRING_CLOUD_EXAMPLE_GROUP

# 扩展配置:优先级大于 shared-configs,在 shared-configs 之后加载
extension-configs:
- data-id: nacos-config-advanced.yaml
group: SPRING_CLOUD_EXAMPLE_GROUP
refresh: true

- data-id: nacos-config-base.yaml
group: SPRING_CLOUD_EXAMPLE_GROUP
refresh: true

我们需要读取多个配置文件的话,可以用共享配置和扩展配置两种方式,扩展配置的优先级高于共享配置。

两种方式看不出本质差别;除了优先级不同以外,也没有其他差别。

配置文件的优先级

Nacos Config可以通过3个方式来从Nacos中拉去配置:

  • A: 通过 spring.cloud.nacos.config.shared-configs 支持多个共享 Data Id 的配置
  • B: 通过 spring.cloud.nacos.config.ext-config.data-id 的方式支持多个扩展 Data Id 的配置
  • C: 通过内部相关规则(应用名、应用名+ Profile )自动生成相关的 Data Id 配置,如nacos-config-dev.yaml

当三种方式共同使用时,他们的一个优先级关系是:A < B < C 。

新版本Nacos配置方式

在2.2.0之后的版本,有新的方式来引入 多个配置文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
spring:
application:
name: nacos-config

cloud:
nacos:
# 配置中心IP
config:
server-addr: 10.211.55.12:8848
# 读取以yaml结尾的配置文件,那就是nacos-config.yaml
file-extension: yaml
namespace: 37731914-6c28-4265-a090-7a9391ed843d
group: DEFAULT_GROUP

# 通过引入的方式,引入多个Nacos 配置文件
import:
- optional:nacos:application-dev.yml
- optional:nacos:application-common-dev.yml
# 引入该配置文件的同时,覆盖到group_01
- optional:nacos:application-common-dev.yml?group=group_01
# 引入该配置文件的同时,覆盖到group_01,不开启动态刷新
- optional:nacos:application-common-dev.yml?group=group_01&refreshEnable=false

5)@RefreshScope实现动态感知

@Value注解可以获取到配置中心的值,但是无法动态感知修改后的值,因为当我们controller去访问的时候,实际上访问的是controller的bean,这个bean的单例对象池里面的属性 在初始化时已经赋值了。

5.1 获取value注解,无法动态刷新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.javaxing.nacosconfig.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
@Value("${aaa}")
private String aaaStr; ;

@GetMapping("/test")
public void Test(){
System.out.println("配置文件的参数值:"+aaaStr);
}
}
image-20230405213044587

Nacos 配置中心修改aaa的值为10

image-20230405213330636

再次访问controller

image-20230405213358139

当我们修改完nacos配置文件后,再次执行controller,会发现这个值并没有动态的发生改变。

因为当我们启动服务时,配置中心的参数会在容器初始化的时候,跟随controller的Bean 初始化时,存入到bean的单例变量池。

5.2 增加注解实现动态感知

1
2
3
4
5
6
7
8
9
10
11
@RestController
@RefreshScope
public class TestController {
@Value("${aaa}")
private String aaaStr; ;

@GetMapping("/test")
public void Test(){
System.out.println("配置文件的参数值:"+aaaStr);
}
}

访问controller,当前的value是10:

image-20230405213755402

我们在nacos修改参数为 30

image-20230405213859001

再次请求controller

image-20230405213956440

如图所示,在不重启服务端的情况下,我们再次请求controller后,拿到的value是最新的30。

并且通过日志可以发现,SpringBoot已经重启了。

5.3 @RefreshScope 导致@Scheduled定时任务失效问题

当利用@RefreshScope刷新配置后会导致定时任务失效,因为当我们刷新配置后,SpringBoot容器已经重启了一次,容器里面的Bean都会被清空。而重启后的是不存在该bean,那么定时任务也会失效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.javaxing.nacosconfig.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RefreshScope
public class TestController implements ApplicationListener<RefreshScopeRefreshedEvent> {
@Value("${aaa}")
private String aaaStr; ;

@GetMapping("/test")
public void Test(){
System.out.println("配置文件的参数值:"+aaaStr);
}


@Scheduled(cron = "*/3 * * * * ?") //定时任务每隔3s执行一次
public void execute() {
System.out.println("定时任务正常执行。。。。。。");
}

@Override
public void onApplicationEvent(RefreshScopeRefreshedEvent refreshScopeRefreshedEvent) {
// 监听到动态刷新的话,就执行 定时任务
this.execute();
}
}

我们可以做一个动态刷新监听,当刷新后 就重新执行 定时任务。

但是实际上,并不推荐 动态刷新,因为刷新时 会刷新bean容器的数据,会导致一些不可预料的问题。