1. Nacos多环境治理概述

Nacos作为阿里巴巴开源的动态服务发现、配置管理和服务管理平台,在企业级应用中扮演着重要角色。多环境治理是Nacos的核心功能之一,能够有效管理开发、测试、生产等不同环境的配置和服务。

1.1 核心功能

  1. 配置管理: 统一管理多环境配置,支持动态更新
  2. 服务发现: 服务注册与发现,支持环境隔离
  3. 命名空间: 环境隔离和资源分组
  4. 配置中心: 集中化配置管理
  5. 服务治理: 健康检查、负载均衡、流量管理

1.2 环境架构

1
2
3
开发环境(dev) → 测试环境(test) → 预生产环境(pre) → 生产环境(prod)
↓ ↓ ↓ ↓
Nacos集群 → 配置管理 → 服务注册 → 环境隔离 → 服务治理

2. Nacos集群部署

2.1 Docker Compose部署

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# docker-compose-nacos.yml
version: '3.8'
services:
nacos1:
image: nacos/nacos-server:v2.2.0
container_name: nacos1
restart: unless-stopped
ports:
- "8848:8848"
- "9848:9848"
environment:
- PREFER_HOST_MODE=hostname
- MODE=cluster
- NACOS_SERVERS=nacos1:8848 nacos2:8848 nacos3:8848
- SPRING_DATASOURCE_PLATFORM=mysql
- MYSQL_SERVICE_HOST=mysql
- MYSQL_SERVICE_PORT=3306
- MYSQL_SERVICE_DB_NAME=nacos
- MYSQL_SERVICE_USER=nacos
- MYSQL_SERVICE_PASSWORD=nacos123
- MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
volumes:
- ./nacos/logs:/home/nacos/logs
- ./nacos/data:/home/nacos/data
networks:
- nacos-network
depends_on:
- mysql

nacos2:
image: nacos/nacos-server:v2.2.0
container_name: nacos2
restart: unless-stopped
ports:
- "8849:8848"
- "9849:9848"
environment:
- PREFER_HOST_MODE=hostname
- MODE=cluster
- NACOS_SERVERS=nacos1:8848 nacos2:8848 nacos3:8848
- SPRING_DATASOURCE_PLATFORM=mysql
- MYSQL_SERVICE_HOST=mysql
- MYSQL_SERVICE_PORT=3306
- MYSQL_SERVICE_DB_NAME=nacos
- MYSQL_SERVICE_USER=nacos
- MYSQL_SERVICE_PASSWORD=nacos123
- MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
volumes:
- ./nacos/logs:/home/nacos/logs
- ./nacos/data:/home/nacos/data
networks:
- nacos-network
depends_on:
- mysql

nacos3:
image: nacos/nacos-server:v2.2.0
container_name: nacos3
restart: unless-stopped
ports:
- "8850:8848"
- "9850:9848"
environment:
- PREFER_HOST_MODE=hostname
- MODE=cluster
- NACOS_SERVERS=nacos1:8848 nacos2:8848 nacos3:8848
- SPRING_DATASOURCE_PLATFORM=mysql
- MYSQL_SERVICE_HOST=mysql
- MYSQL_SERVICE_PORT=3306
- MYSQL_SERVICE_DB_NAME=nacos
- MYSQL_SERVICE_USER=nacos
- MYSQL_SERVICE_PASSWORD=nacos123
- MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
volumes:
- ./nacos/logs:/home/nacos/logs
- ./nacos/data:/home/nacos/data
networks:
- nacos-network
depends_on:
- mysql

mysql:
image: mysql:8.0
container_name: nacos-mysql
restart: unless-stopped
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=root123
- MYSQL_DATABASE=nacos
- MYSQL_USER=nacos
- MYSQL_PASSWORD=nacos123
volumes:
- mysql_data:/var/lib/mysql
- ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- nacos-network

volumes:
mysql_data:

networks:
nacos-network:
driver: bridge

2.2 MySQL初始化脚本

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
-- init.sql
-- 创建Nacos数据库
CREATE DATABASE IF NOT EXISTS nacos DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

USE nacos;

-- 创建配置信息表
CREATE TABLE `config_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(255) DEFAULT NULL,
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`app_name` varchar(128) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`c_desc` varchar(256) DEFAULT NULL,
`c_use` varchar(64) DEFAULT NULL,
`effect` varchar(64) DEFAULT NULL,
`type` varchar(64) DEFAULT NULL,
`c_schema` text,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';

-- 创建服务信息表
CREATE TABLE `service_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` varchar(128) NOT NULL COMMENT '服务名称',
`group_name` varchar(128) NOT NULL COMMENT '分组名称',
`namespace_id` varchar(128) NOT NULL COMMENT '命名空间ID',
`protect_threshold` float NOT NULL DEFAULT '0' COMMENT '保护阈值',
`metadata` text COMMENT '元数据',
`selector` text COMMENT '选择器',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_service_name_group_namespace` (`name`,`group_name`,`namespace_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='service_info';

-- 创建实例信息表
CREATE TABLE `instance_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`service_name` varchar(128) NOT NULL COMMENT '服务名称',
`group_name` varchar(128) NOT NULL COMMENT '分组名称',
`namespace_id` varchar(128) NOT NULL COMMENT '命名空间ID',
`ip` varchar(50) NOT NULL COMMENT 'IP地址',
`port` int(11) NOT NULL COMMENT '端口',
`weight` double NOT NULL DEFAULT '1' COMMENT '权重',
`healthy` tinyint(1) NOT NULL DEFAULT '1' COMMENT '健康状态',
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用',
`ephemeral` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否临时实例',
`metadata` text COMMENT '元数据',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_instance_service_group_namespace_ip_port` (`service_name`,`group_name`,`namespace_id`,`ip`,`port`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='instance_info';

3. 多环境配置管理

3.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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/**
* 环境配置枚举
*/
public enum Environment {
DEV("dev", "开发环境", "http://localhost:8848"),
TEST("test", "测试环境", "http://test-nacos:8848"),
PRE("pre", "预生产环境", "http://pre-nacos:8848"),
PROD("prod", "生产环境", "http://prod-nacos:8848");

private final String code;
private final String name;
private final String serverAddr;

Environment(String code, String name, String serverAddr) {
this.code = code;
this.name = name;
this.serverAddr = serverAddr;
}

public String getCode() { return code; }
public String getName() { return name; }
public String getServerAddr() { return serverAddr; }

/**
* 根据代码获取环境
*/
public static Environment fromCode(String code) {
for (Environment env : values()) {
if (env.getCode().equals(code)) {
return env;
}
}
throw new IllegalArgumentException("未知环境代码: " + code);
}
}

/**
* Nacos配置属性
*/
@Data
@Component
@ConfigurationProperties(prefix = "nacos")
public class NacosProperties {

/**
* 当前环境
*/
private String environment = "dev";

/**
* 服务地址
*/
private String serverAddr = "localhost:8848";

/**
* 命名空间
*/
private String namespace;

/**
* 分组
*/
private String group = "DEFAULT_GROUP";

/**
* 用户名
*/
private String username = "nacos";

/**
* 密码
*/
private String password = "nacos";

/**
* 配置刷新
*/
private boolean refreshEnabled = true;

/**
* 服务发现
*/
private Discovery discovery = new Discovery();

/**
* 配置中心
*/
private Config config = new Config();

@Data
public static class Discovery {
/**
* 服务名称
*/
private String serviceName;

/**
* 服务端口
*/
private int port = 8080;

/**
* 服务IP
*/
private String ip;

/**
* 权重
*/
private double weight = 1.0;

/**
* 是否临时实例
*/
private boolean ephemeral = true;

/**
* 健康检查
*/
private HealthCheck healthCheck = new HealthCheck();
}

@Data
public static class Config {
/**
* 配置数据ID
*/
private String dataId;

/**
* 配置分组
*/
private String group = "DEFAULT_GROUP";

/**
* 配置格式
*/
private String fileExtension = "properties";

/**
* 是否自动刷新
*/
private boolean autoRefresh = true;
}

@Data
public static class HealthCheck {
/**
* 健康检查路径
*/
private String path = "/actuator/health";

/**
* 健康检查间隔
*/
private int interval = 5000;

/**
* 健康检查超时
*/
private int timeout = 3000;

/**
* 不健康阈值
*/
private int unhealthyThreshold = 3;

/**
* 健康阈值
*/
private int healthyThreshold = 2;
}
}

3.2 环境配置服务

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
/**
* 环境配置服务
*/
@Service
public class EnvironmentConfigService {

@Autowired
private NacosProperties nacosProperties;

@Autowired
private ConfigService configService;

/**
* 获取当前环境
*/
public Environment getCurrentEnvironment() {
return Environment.fromCode(nacosProperties.getEnvironment());
}

/**
* 获取环境配置
*/
public String getEnvironmentConfig(String dataId) {
try {
return configService.getConfig(
dataId,
nacosProperties.getGroup(),
5000
);
} catch (NacosException e) {
throw new RuntimeException("获取配置失败: " + dataId, e);
}
}

/**
* 获取环境配置(带默认值)
*/
public String getEnvironmentConfig(String dataId, String defaultValue) {
try {
String config = configService.getConfig(
dataId,
nacosProperties.getGroup(),
5000
);
return StringUtils.hasText(config) ? config : defaultValue;
} catch (NacosException e) {
log.warn("获取配置失败,使用默认值: dataId={}, defaultValue={}", dataId, defaultValue);
return defaultValue;
}
}

/**
* 发布环境配置
*/
public boolean publishEnvironmentConfig(String dataId, String content) {
try {
return configService.publishConfig(
dataId,
nacosProperties.getGroup(),
content
);
} catch (NacosException e) {
log.error("发布配置失败: dataId={}", dataId, e);
return false;
}
}

/**
* 删除环境配置
*/
public boolean removeEnvironmentConfig(String dataId) {
try {
return configService.removeConfig(
dataId,
nacosProperties.getGroup()
);
} catch (NacosException e) {
log.error("删除配置失败: dataId={}", dataId, e);
return false;
}
}

/**
* 监听配置变化
*/
public void addConfigListener(String dataId, ConfigChangeListener listener) {
try {
configService.addListener(
dataId,
nacosProperties.getGroup(),
listener
);
} catch (NacosException e) {
log.error("添加配置监听器失败: dataId={}", dataId, e);
}
}

/**
* 获取环境特定配置
*/
public Map<String, Object> getEnvironmentSpecificConfig() {
Environment env = getCurrentEnvironment();
Map<String, Object> config = new HashMap<>();

// 数据库配置
config.put("spring.datasource.url", getDatabaseUrl(env));
config.put("spring.datasource.username", getDatabaseUsername(env));
config.put("spring.datasource.password", getDatabasePassword(env));

// Redis配置
config.put("spring.redis.host", getRedisHost(env));
config.put("spring.redis.port", getRedisPort(env));
config.put("spring.redis.password", getRedisPassword(env));

// 日志配置
config.put("logging.level.root", getLogLevel(env));
config.put("logging.file.name", getLogFile(env));

return config;
}

private String getDatabaseUrl(Environment env) {
return getEnvironmentConfig(
"database.url." + env.getCode(),
"jdbc:mysql://localhost:3306/demo_" + env.getCode()
);
}

private String getDatabaseUsername(Environment env) {
return getEnvironmentConfig(
"database.username." + env.getCode(),
"demo_" + env.getCode()
);
}

private String getDatabasePassword(Environment env) {
return getEnvironmentConfig(
"database.password." + env.getCode(),
"demo123"
);
}

private String getRedisHost(Environment env) {
return getEnvironmentConfig(
"redis.host." + env.getCode(),
"localhost"
);
}

private int getRedisPort(Environment env) {
String port = getEnvironmentConfig(
"redis.port." + env.getCode(),
"6379"
);
return Integer.parseInt(port);
}

private String getRedisPassword(Environment env) {
return getEnvironmentConfig(
"redis.password." + env.getCode(),
""
);
}

private String getLogLevel(Environment env) {
return getEnvironmentConfig(
"logging.level." + env.getCode(),
env == Environment.PROD ? "WARN" : "DEBUG"
);
}

private String getLogFile(Environment env) {
return getEnvironmentConfig(
"logging.file." + env.getCode(),
"/var/log/demo_" + env.getCode() + ".log"
);
}
}

4. 服务注册与发现

4.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
/**
* 服务注册配置
*/
@Configuration
@EnableDiscoveryClient
public class ServiceRegistrationConfig {

@Autowired
private NacosProperties nacosProperties;

/**
* Nacos服务发现配置
*/
@Bean
public NacosDiscoveryProperties nacosDiscoveryProperties() {
NacosDiscoveryProperties properties = new NacosDiscoveryProperties();

// 基础配置
properties.setServerAddr(nacosProperties.getServerAddr());
properties.setNamespace(nacosProperties.getNamespace());
properties.setGroup(nacosProperties.getGroup());
properties.setUsername(nacosProperties.getUsername());
properties.setPassword(nacosProperties.getPassword());

// 服务配置
properties.setServiceName(nacosProperties.getDiscovery().getServiceName());
properties.setIp(nacosProperties.getDiscovery().getIp());
properties.setPort(nacosProperties.getDiscovery().getPort());
properties.setWeight(nacosProperties.getDiscovery().getWeight());
properties.setEphemeral(nacosProperties.getDiscovery().isEphemeral());

// 健康检查配置
HealthCheck healthCheck = nacosProperties.getDiscovery().getHealthCheck();
properties.setHealthCheckUrl(healthCheck.getPath());
properties.setHeartBeatInterval(healthCheck.getInterval());
properties.setHeartBeatTimeout(healthCheck.getTimeout());
properties.setIpDeleteTimeout(healthCheck.getUnhealthyThreshold() * healthCheck.getInterval());

// 元数据配置
Map<String, String> metadata = new HashMap<>();
metadata.put("environment", nacosProperties.getEnvironment());
metadata.put("version", "1.0.0");
metadata.put("region", "beijing");
properties.setMetadata(metadata);

return properties;
}

/**
* 服务注册Bean
*/
@Bean
public NacosServiceRegistry nacosServiceRegistry(
NacosDiscoveryProperties nacosDiscoveryProperties) {
return new NacosServiceRegistry(nacosDiscoveryProperties);
}

/**
* 服务发现Bean
*/
@Bean
public NacosServiceDiscovery nacosServiceDiscovery(
NacosDiscoveryProperties nacosDiscoveryProperties) {
return new NacosServiceDiscovery(nacosDiscoveryProperties);
}
}

4.2 服务管理服务

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/**
* 服务管理服务
*/
@Service
public class ServiceManagementService {

@Autowired
private NamingService namingService;

@Autowired
private NacosProperties nacosProperties;

/**
* 注册服务实例
*/
public void registerServiceInstance(String serviceName, String ip, int port) {
try {
Instance instance = new Instance();
instance.setIp(ip);
instance.setPort(port);
instance.setWeight(nacosProperties.getDiscovery().getWeight());
instance.setEphemeral(nacosProperties.getDiscovery().isEphemeral());

// 设置元数据
Map<String, String> metadata = new HashMap<>();
metadata.put("environment", nacosProperties.getEnvironment());
metadata.put("version", "1.0.0");
metadata.put("startup.time", String.valueOf(System.currentTimeMillis()));
instance.setMetadata(metadata);

// 注册服务实例
namingService.registerInstance(
serviceName,
nacosProperties.getGroup(),
instance
);

log.info("服务实例注册成功: serviceName={}, ip={}, port={}",
serviceName, ip, port);

} catch (NacosException e) {
log.error("服务实例注册失败: serviceName={}, ip={}, port={}",
serviceName, ip, port, e);
throw new RuntimeException("服务注册失败", e);
}
}

/**
* 注销服务实例
*/
public void deregisterServiceInstance(String serviceName, String ip, int port) {
try {
namingService.deregisterInstance(
serviceName,
nacosProperties.getGroup(),
ip,
port
);

log.info("服务实例注销成功: serviceName={}, ip={}, port={}",
serviceName, ip, port);

} catch (NacosException e) {
log.error("服务实例注销失败: serviceName={}, ip={}, port={}",
serviceName, ip, port, e);
throw new RuntimeException("服务注销失败", e);
}
}

/**
* 获取服务实例列表
*/
public List<Instance> getServiceInstances(String serviceName) {
try {
return namingService.getAllInstances(
serviceName,
nacosProperties.getGroup()
);
} catch (NacosException e) {
log.error("获取服务实例列表失败: serviceName={}", serviceName, e);
return Collections.emptyList();
}
}

/**
* 获取健康服务实例列表
*/
public List<Instance> getHealthyServiceInstances(String serviceName) {
try {
return namingService.selectInstances(
serviceName,
nacosProperties.getGroup(),
true
);
} catch (NacosException e) {
log.error("获取健康服务实例列表失败: serviceName={}", serviceName, e);
return Collections.emptyList();
}
}

/**
* 获取服务实例(负载均衡)
*/
public Instance getServiceInstance(String serviceName) {
try {
return namingService.selectOneHealthyInstance(
serviceName,
nacosProperties.getGroup()
);
} catch (NacosException e) {
log.error("获取服务实例失败: serviceName={}", serviceName, e);
return null;
}
}

/**
* 订阅服务变化
*/
public void subscribeService(String serviceName, EventListener listener) {
try {
namingService.subscribe(
serviceName,
nacosProperties.getGroup(),
listener
);

log.info("订阅服务成功: serviceName={}", serviceName);

} catch (NacosException e) {
log.error("订阅服务失败: serviceName={}", serviceName, e);
}
}

/**
* 取消订阅服务
*/
public void unsubscribeService(String serviceName, EventListener listener) {
try {
namingService.unsubscribe(
serviceName,
nacosProperties.getGroup(),
listener
);

log.info("取消订阅服务成功: serviceName={}", serviceName);

} catch (NacosException e) {
log.error("取消订阅服务失败: serviceName={}", serviceName, e);
}
}
}

4.3 服务健康检查

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
/**
* 服务健康检查
*/
@Component
public class ServiceHealthChecker {

@Autowired
private ServiceManagementService serviceManagementService;

@Autowired
private NacosProperties nacosProperties;

private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5);

@PostConstruct
public void startHealthCheck() {
// 启动健康检查任务
scheduler.scheduleWithFixedDelay(
this::performHealthCheck,
0,
30,
TimeUnit.SECONDS
);
}

/**
* 执行健康检查
*/
private void performHealthCheck() {
try {
String serviceName = nacosProperties.getDiscovery().getServiceName();
List<Instance> instances = serviceManagementService.getServiceInstances(serviceName);

for (Instance instance : instances) {
checkInstanceHealth(instance);
}

} catch (Exception e) {
log.error("健康检查执行失败", e);
}
}

/**
* 检查实例健康状态
*/
private void checkInstanceHealth(Instance instance) {
try {
String healthUrl = "http://" + instance.getIp() + ":" + instance.getPort() +
nacosProperties.getDiscovery().getHealthCheck().getPath();

// 发送健康检查请求
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.getForEntity(healthUrl, String.class);

if (response.getStatusCode().is2xxSuccessful()) {
log.debug("实例健康检查通过: {}:{}", instance.getIp(), instance.getPort());
} else {
log.warn("实例健康检查失败: {}:{}, status={}",
instance.getIp(), instance.getPort(), response.getStatusCode());
}

} catch (Exception e) {
log.warn("实例健康检查异常: {}:{}, error={}",
instance.getIp(), instance.getPort(), e.getMessage());
}
}

@PreDestroy
public void shutdown() {
scheduler.shutdown();
}
}

5. 动态配置管理

5.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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/**
* 配置变化监听器
*/
@Component
public class ConfigChangeListener implements Listener {

private static final Logger log = LoggerFactory.getLogger(ConfigChangeListener.class);

@Autowired
private EnvironmentConfigService environmentConfigService;

@Autowired
private ApplicationEventPublisher eventPublisher;

/**
* 配置变化回调
*/
@Override
public void receiveConfigInfo(String configInfo) {
log.info("收到配置变化通知: {}", configInfo);

try {
// 解析配置信息
Map<String, Object> configMap = parseConfigInfo(configInfo);

// 发布配置变化事件
ConfigChangeEvent event = new ConfigChangeEvent(this, configMap);
eventPublisher.publishEvent(event);

// 处理配置变化
handleConfigChange(configMap);

} catch (Exception e) {
log.error("处理配置变化失败", e);
}
}

/**
* 解析配置信息
*/
private Map<String, Object> parseConfigInfo(String configInfo) {
Map<String, Object> configMap = new HashMap<>();

try {
// 尝试解析为Properties格式
Properties properties = new Properties();
properties.load(new StringReader(configInfo));

for (String key : properties.stringPropertyNames()) {
configMap.put(key, properties.getProperty(key));
}

} catch (Exception e) {
log.warn("解析配置信息失败,使用原始内容: {}", e.getMessage());
configMap.put("raw", configInfo);
}

return configMap;
}

/**
* 处理配置变化
*/
private void handleConfigChange(Map<String, Object> configMap) {
// 处理数据库配置变化
if (configMap.containsKey("spring.datasource.url")) {
handleDatabaseConfigChange(configMap);
}

// 处理Redis配置变化
if (configMap.containsKey("spring.redis.host")) {
handleRedisConfigChange(configMap);
}

// 处理日志配置变化
if (configMap.containsKey("logging.level.root")) {
handleLoggingConfigChange(configMap);
}
}

/**
* 处理数据库配置变化
*/
private void handleDatabaseConfigChange(Map<String, Object> configMap) {
log.info("数据库配置发生变化");
// 这里可以实现数据库连接池的动态更新
}

/**
* 处理Redis配置变化
*/
private void handleRedisConfigChange(Map<String, Object> configMap) {
log.info("Redis配置发生变化");
// 这里可以实现Redis连接池的动态更新
}

/**
* 处理日志配置变化
*/
private void handleLoggingConfigChange(Map<String, Object> configMap) {
log.info("日志配置发生变化");
// 这里可以实现日志级别的动态更新
}
}

/**
* 配置变化事件
*/
public class ConfigChangeEvent extends ApplicationEvent {

private final Map<String, Object> configMap;

public ConfigChangeEvent(Object source, Map<String, Object> configMap) {
super(source);
this.configMap = configMap;
}

public Map<String, Object> getConfigMap() {
return configMap;
}
}

5.2 配置管理控制器

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/**
* 配置管理控制器
*/
@RestController
@RequestMapping("/api/config")
public class ConfigManagementController {

@Autowired
private EnvironmentConfigService environmentConfigService;

@Autowired
private ServiceManagementService serviceManagementService;

/**
* 获取当前环境配置
*/
@GetMapping("/environment")
public ResponseEntity<Map<String, Object>> getEnvironmentConfig() {
try {
Map<String, Object> config = environmentConfigService.getEnvironmentSpecificConfig();
return ResponseEntity.ok(config);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Collections.singletonMap("error", e.getMessage()));
}
}

/**
* 获取指定配置
*/
@GetMapping("/{dataId}")
public ResponseEntity<String> getConfig(@PathVariable String dataId) {
try {
String config = environmentConfigService.getEnvironmentConfig(dataId);
return ResponseEntity.ok(config);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("获取配置失败: " + e.getMessage());
}
}

/**
* 发布配置
*/
@PostMapping("/{dataId}")
public ResponseEntity<String> publishConfig(
@PathVariable String dataId,
@RequestBody String content) {
try {
boolean success = environmentConfigService.publishEnvironmentConfig(dataId, content);
if (success) {
return ResponseEntity.ok("配置发布成功");
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("配置发布失败");
}
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("配置发布失败: " + e.getMessage());
}
}

/**
* 删除配置
*/
@DeleteMapping("/{dataId}")
public ResponseEntity<String> deleteConfig(@PathVariable String dataId) {
try {
boolean success = environmentConfigService.removeEnvironmentConfig(dataId);
if (success) {
return ResponseEntity.ok("配置删除成功");
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("配置删除失败");
}
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("配置删除失败: " + e.getMessage());
}
}

/**
* 获取服务实例列表
*/
@GetMapping("/services/{serviceName}/instances")
public ResponseEntity<List<Instance>> getServiceInstances(@PathVariable String serviceName) {
try {
List<Instance> instances = serviceManagementService.getServiceInstances(serviceName);
return ResponseEntity.ok(instances);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Collections.emptyList());
}
}

/**
* 获取健康服务实例列表
*/
@GetMapping("/services/{serviceName}/healthy-instances")
public ResponseEntity<List<Instance>> getHealthyServiceInstances(@PathVariable String serviceName) {
try {
List<Instance> instances = serviceManagementService.getHealthyServiceInstances(serviceName);
return ResponseEntity.ok(instances);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Collections.emptyList());
}
}
}

6. 环境切换和治理

6.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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/**
* 环境切换服务
*/
@Service
public class EnvironmentSwitchService {

@Autowired
private EnvironmentConfigService environmentConfigService;

@Autowired
private ServiceManagementService serviceManagementService;

/**
* 切换到指定环境
*/
public void switchToEnvironment(String environmentCode) {
try {
Environment targetEnv = Environment.fromCode(environmentCode);
Environment currentEnv = environmentConfigService.getCurrentEnvironment();

log.info("开始切换环境: {} -> {}", currentEnv.getCode(), targetEnv.getCode());

// 1. 更新环境配置
updateEnvironmentConfig(targetEnv);

// 2. 重新注册服务
reregisterServices(targetEnv);

// 3. 更新配置监听
updateConfigListeners(targetEnv);

log.info("环境切换完成: {}", targetEnv.getCode());

} catch (Exception e) {
log.error("环境切换失败: {}", environmentCode, e);
throw new RuntimeException("环境切换失败", e);
}
}

/**
* 更新环境配置
*/
private void updateEnvironmentConfig(Environment environment) {
// 更新数据库配置
String dbUrl = "jdbc:mysql://" + environment.getServerAddr() + ":3306/demo_" + environment.getCode();
environmentConfigService.publishEnvironmentConfig(
"database.url." + environment.getCode(),
dbUrl
);

// 更新Redis配置
String redisHost = environment.getServerAddr().replace("http://", "").split(":")[0];
environmentConfigService.publishEnvironmentConfig(
"redis.host." + environment.getCode(),
redisHost
);

// 更新日志配置
String logLevel = environment == Environment.PROD ? "WARN" : "DEBUG";
environmentConfigService.publishEnvironmentConfig(
"logging.level." + environment.getCode(),
logLevel
);
}

/**
* 重新注册服务
*/
private void reregisterServices(Environment environment) {
// 获取当前服务实例
String serviceName = "demo-service";
List<Instance> instances = serviceManagementService.getServiceInstances(serviceName);

for (Instance instance : instances) {
// 注销旧实例
serviceManagementService.deregisterServiceInstance(
serviceName,
instance.getIp(),
instance.getPort()
);

// 注册新实例(使用新环境配置)
serviceManagementService.registerServiceInstance(
serviceName,
instance.getIp(),
instance.getPort()
);
}
}

/**
* 更新配置监听
*/
private void updateConfigListeners(Environment environment) {
// 这里可以实现配置监听器的更新逻辑
log.info("更新配置监听器: {}", environment.getCode());
}
}

6.2 环境治理工具

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/**
* 环境治理工具
*/
@Component
public class EnvironmentGovernanceTool {

@Autowired
private EnvironmentConfigService environmentConfigService;

@Autowired
private ServiceManagementService serviceManagementService;

/**
* 环境健康检查
*/
public EnvironmentHealthReport checkEnvironmentHealth() {
EnvironmentHealthReport report = new EnvironmentHealthReport();

try {
// 检查配置中心健康状态
boolean configHealthy = checkConfigCenterHealth();
report.setConfigCenterHealthy(configHealthy);

// 检查服务注册中心健康状态
boolean serviceHealthy = checkServiceRegistryHealth();
report.setServiceRegistryHealthy(serviceHealthy);

// 检查服务实例健康状态
Map<String, List<Instance>> serviceInstances = checkServiceInstancesHealth();
report.setServiceInstances(serviceInstances);

// 计算整体健康状态
boolean overallHealthy = configHealthy && serviceHealthy;
report.setOverallHealthy(overallHealthy);

} catch (Exception e) {
log.error("环境健康检查失败", e);
report.setOverallHealthy(false);
report.setErrorMessage(e.getMessage());
}

return report;
}

/**
* 检查配置中心健康状态
*/
private boolean checkConfigCenterHealth() {
try {
String testConfig = environmentConfigService.getEnvironmentConfig("health.check", "ok");
return "ok".equals(testConfig);
} catch (Exception e) {
log.error("配置中心健康检查失败", e);
return false;
}
}

/**
* 检查服务注册中心健康状态
*/
private boolean checkServiceRegistryHealth() {
try {
List<Instance> instances = serviceManagementService.getServiceInstances("demo-service");
return !instances.isEmpty();
} catch (Exception e) {
log.error("服务注册中心健康检查失败", e);
return false;
}
}

/**
* 检查服务实例健康状态
*/
private Map<String, List<Instance>> checkServiceInstancesHealth() {
Map<String, List<Instance>> result = new HashMap<>();

try {
String[] services = {"demo-service", "user-service", "order-service"};

for (String serviceName : services) {
List<Instance> healthyInstances = serviceManagementService.getHealthyServiceInstances(serviceName);
result.put(serviceName, healthyInstances);
}

} catch (Exception e) {
log.error("服务实例健康检查失败", e);
}

return result;
}

/**
* 环境配置同步
*/
public void syncEnvironmentConfig(String sourceEnv, String targetEnv) {
try {
log.info("开始同步环境配置: {} -> {}", sourceEnv, targetEnv);

// 获取源环境配置
Map<String, Object> sourceConfig = environmentConfigService.getEnvironmentSpecificConfig();

// 同步到目标环境
for (Map.Entry<String, Object> entry : sourceConfig.entrySet()) {
String key = entry.getKey();
String value = entry.getValue().toString();

// 替换环境标识
String targetKey = key.replace(sourceEnv, targetEnv);
environmentConfigService.publishEnvironmentConfig(targetKey, value);
}

log.info("环境配置同步完成: {} -> {}", sourceEnv, targetEnv);

} catch (Exception e) {
log.error("环境配置同步失败: {} -> {}", sourceEnv, targetEnv, e);
throw new RuntimeException("环境配置同步失败", e);
}
}
}

/**
* 环境健康报告
*/
@Data
public class EnvironmentHealthReport {
private boolean overallHealthy;
private boolean configCenterHealthy;
private boolean serviceRegistryHealthy;
private Map<String, List<Instance>> serviceInstances;
private String errorMessage;
private LocalDateTime checkTime;

public EnvironmentHealthReport() {
this.checkTime = LocalDateTime.now();
}
}

7. 应用配置

7.1 application.yml配置

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
# application.yml
spring:
application:
name: demo-service
profiles:
active: dev
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: ${spring.profiles.active}
group: DEFAULT_GROUP
service-name: ${spring.application.name}
ip: ${spring.cloud.nacos.discovery.ip:}
port: ${server.port}
weight: 1.0
ephemeral: true
metadata:
version: 1.0.0
region: beijing
config:
server-addr: localhost:8848
namespace: ${spring.profiles.active}
group: DEFAULT_GROUP
file-extension: properties
refresh-enabled: true
shared-configs:
- data-id: common-config.properties
group: SHARED_GROUP
refresh: true
- data-id: database-config.properties
group: SHARED_GROUP
refresh: true
extension-configs:
- data-id: ${spring.application.name}-config.properties
group: DEFAULT_GROUP
refresh: true

server:
port: 8080

management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always

logging:
level:
com.alibaba.nacos: INFO
com.example: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

7.2 环境特定配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# dev环境配置
# database-config.properties
spring.datasource.url=jdbc:mysql://localhost:3306/demo_dev?useSSL=false&serverTimezone=UTC
spring.datasource.username=demo_dev
spring.datasource.password=demo123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# redis-config.properties
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.database=0

# logging-config.properties
logging.level.root=DEBUG
logging.file.name=/var/log/demo_dev.log
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# prod环境配置
# database-config.properties
spring.datasource.url=jdbc:mysql://prod-mysql:3306/demo_prod?useSSL=true&serverTimezone=UTC
spring.datasource.username=demo_prod
spring.datasource.password=prod123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# redis-config.properties
spring.redis.host=prod-redis
spring.redis.port=6379
spring.redis.password=prod123
spring.redis.database=0

# logging-config.properties
logging.level.root=WARN
logging.file.name=/var/log/demo_prod.log

8. 总结

通过Nacos多环境治理,我们成功构建了一个完整的环境管理解决方案。关键特性包括:

8.1 核心优势

  1. 环境隔离: 通过命名空间实现环境隔离
  2. 配置管理: 集中化配置管理,支持动态更新
  3. 服务治理: 服务注册发现,健康检查
  4. 环境切换: 支持环境间的快速切换
  5. 监控治理: 完善的环境监控和治理工具

8.2 最佳实践

  1. 命名空间规划: 合理规划环境命名空间
  2. 配置分层: 公共配置和私有配置分离
  3. 服务监控: 完善的服务健康检查
  4. 环境同步: 环境间配置同步机制
  5. 安全控制: 环境访问权限控制

这套Nacos多环境治理方案不仅能够满足企业级应用的环境管理需求,还为微服务架构提供了强有力的基础设施支持。