Nacos生产环境高可用与最佳实践
大家好,我是joker,希望你快乐。
上一篇Nacos2.x核心原理分析中从源码层面分析了 Nacos 的工作机制,这篇我们聚焦生产环境,看看 Nacos 在实际落地中会遇到哪些问题,以及如何构建一个高可用、安全、可运维的 Nacos 体系。
集群部署与高可用
单节点 Nacos 存在单点故障风险,生产环境必须集群部署。
集群架构
+-----------+
| Nginx |
| SLB | <-- 负载均衡
+-----------+
/ | \
+--------+ +--------+ +--------+
| Nacos1 | | Nacos2 | | Nacos3 | <-- 至少3节点
+--------+ +--------+ +--------+
\ | /
+-------+-------+
| MySQL | <-- 主从或集群
+---------------+
集群配置要点
conf/cluster.conf配置所有节点
192.168.1.101:8848
192.168.1.102:8848
192.168.1.103:8848
conf/application.properties配置 MySQL 持久化
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://192.168.1.100:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=nacos
db.password.0=nacos_password
- Nginx 负载均衡配置
upstream nacos_cluster {
server 192.168.1.101:8848;
server 192.168.1.102:8848;
server 192.168.1.103:8848;
}
server {
listen 8848;
location / {
proxy_pass http://nacos_cluster;
}
}
- 启动集群
sh startup.sh -m cluster
MySQL 高可用
Nacos 集群的数据一致性依赖 MySQL,MySQL 本身的高可用同样关键:
- 主从复制:读写分离,主库故障时切换到从库
- 分片集群:大规模数据场景,按行或列分片
- 多主多从:高可用与负载均衡兼顾
生产环境建议 MySQL 至少主从部署,定期全量+增量备份。
端口规划
Nacos 2.x 需要开放以下端口:
| 端口 | 用途 |
|---|---|
| 8848 | HTTP API / Web 控制台 |
| 9848 | gRPC 客户端通信(8848 + 1000) |
| 9849 | gRPC 集群内部通信(8848 + 1001) |
| 7848 | Raft 协议通信(8848 - 1000) |
服务治理进阶
保护阈值
当服务健康实例比例低于保护阈值时,Nacos 会将所有实例(包括不健康的)返回给消费者,避免流量全部涌向少量健康实例导致雪崩。
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: prod
cluster-name: BJ
weight: 1.0
在 Nacos 控制台的服务详情页面可以设置保护阈值,建议值 0.3-0.5。
保护阈值是一种宁可返回不健康的实例,也不让所有请求失败的兜底策略。消费者拿到不健康的实例后,可能会调用失败,但至少有重试的机会,而不是直接无实例可用。
同集群优先调用
Nacos 支持按集群(Cluster)分组服务实例,消费者优先调用同集群的实例,降低网络延迟。
- 服务提供者指定集群名
spring:
cloud:
nacos:
discovery:
cluster-name: BJ
-
服务消费者也指定集群名,调用时优先匹配同集群实例
-
同集群无可用实例时,才会调用其他集群的实例
这种策略在多机房部署场景下非常实用:北京机房的消费者优先调用北京机房的实例,只有北京机房全部不可用时才跨机房调用。
权重路由
Nacos 支持为实例设置权重,权重越高被选中的概率越大。常见应用场景:
- 灰度发布:新版本实例设置低权重,逐步提高
- 流量迁移:旧集群权重逐步降低,新集群逐步提高
- 性能差异化:高配机器设置高权重,低配机器设置低权重
配置中心进阶
多环境隔离
Nacos 通过 Namespace + Group + Data ID 三级结构实现配置隔离:
| 维度 | 作用 | 示例 |
|---|---|---|
| Namespace | 环境隔离 | dev / test / prod |
| Group | 业务分组 | ORDER_GROUP / USER_GROUP |
| Data ID | 配置文件 | order-service-prod.yaml |
在 Nacos 控制台创建 Namespace 时会生成一个 ID,客户端配置时必须使用这个 ID 而非名称:
spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
namespace: a1b2c3d4-5678-90ab-cdef-1234567890ab
group: ORDER_GROUP
file-extension: yaml
配置版本管理与回滚
Nacos 自动保存配置的历史版本,包括修改人、修改时间和变更内容。在控制台的"历史版本"页面可以:
- 查看配置变更记录
- 对比不同版本的差异
- 一键回滚到任意历史版本
建议在每次重要配置变更前,记录当前配置的关键参数,方便回滚时验证。
共享配置与扩展配置
当多个服务有相同的配置(如数据库连接、Redis 地址)时,可以使用共享配置避免重复维护:
spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
shared-configs:
- data-id: common-db.yaml
group: SHARED_GROUP
refresh: true
- data-id: common-redis.yaml
group: SHARED_GROUP
refresh: true
extension-configs:
- data-id: custom-config.yaml
group: APP_GROUP
refresh: true
配置加载优先级(从高到低):
- 带 profile 的配置:
${spring.application.name}-${profile}.${file-extension} - 不带 profile 的配置:
${spring.application.name}.${file-extension} - extension-configs(按数组顺序,后者优先级更高)
- shared-configs(按数组顺序,后者优先级更高)
权限控制
生产环境必须开启 Nacos 的鉴权,防止未授权访问。
开启鉴权
在 application.properties 中配置,生产环境不能使用默认值:
nacos.core.auth.enabled=true
nacos.core.auth.plugin.nacos.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
nacos.core.auth.server.identity.key=serverIdentity
nacos.core.auth.server.identity.value=security
RBAC 权限模型
Nacos 提供用户、角色、权限三级授权:
- 用户:登录控制台的账号
- 角色:权限的集合
- 权限:对某个 Namespace 下资源的读写权限
建议按环境分配权限:开发人员只能访问 dev 命名空间,运维人员才能访问 prod 命名空间。
监控与运维
关键监控指标
| 指标 | 说明 | 告警阈值建议 |
|---|---|---|
| 注册实例数 | 各服务的实例数量 | 突降超过 30% |
| 配置变更频率 | 单位时间内配置变更次数 | 短时间大量变更 |
| gRPC 连接数 | 当前活跃的客户端连接 | 接近最大连接数 |
| JVM 堆内存 | Nacos 进程的堆内存使用率 | > 80% |
| MySQL 连接池 | 数据库连接池使用率 | > 80% |
| Raft Leader 状态 | 当前 Leader 是否存在 | Leader 丢失 |
Prometheus + Grafana 监控
Nacos 2.x 暴露了 Prometheus 指标端点,在 application.properties 中开启:
management.endpoints.web.exposure.include=*
配合 Grafana 仪表盘可以实时监控 Nacos 的运行状态。
日志管理
Nacos 的日志文件位于 logs/ 目录下:
| 日志文件 | 内容 |
|---|---|
| nacos.log | 主日志 |
| config.log | 配置中心日志 |
| naming.log | 注册中心日志 |
| remote.log | gRPC 通信日志 |
生产环境建议开启日志轮转,定期清理过期日志,或使用 ELK Stack 集中收集分析。
常见问题与排查
服务注册失败
排查步骤:
- 检查 Nacos Server 是否正常运行:
curl http://localhost:8848/nacos/v1/ns/service/list?pageNo=1&pageSize=10 - 检查网络连通性和端口:8848(HTTP)、9848(gRPC)
- 检查客户端配置:
server-addr、namespace是否正确 - 检查依赖版本:Spring Boot、Spring Cloud、Spring Cloud Alibaba 三者版本是否兼容
配置未生效
排查步骤:
- 检查 Data ID 是否正确:默认规则为
${spring.application.name}.${file-extension} - 检查 Namespace 是否使用了 ID 而非名称
- 检查是否需要
@RefreshScope注解 - 检查
bootstrap.yml是否生效:Spring Boot 2.4+ 需要引入spring-cloud-starter-bootstrap - 检查配置优先级:profile 配置 > 默认配置 > shared-configs
集群节点无法加入
排查步骤:
- 检查
cluster.conf中 IP 是否正确,不能使用127.0.0.1,必须使用实际 IP - 检查 7848、8848、9848、9849 端口是否全部开放
- 检查所有节点的 MySQL 配置是否一致
- 检查节点时间是否同步(Raft 协议依赖时间)
@RefreshScope 与 @Scheduled 冲突
当一个 Bean 同时使用 @RefreshScope 和 @Scheduled 时,配置刷新后定时任务会失效。这里提供两种解决方案。
方案一:配置类分离(推荐)
将动态配置抽取到单独的 @RefreshScope Bean 中,定时任务类不再加 @RefreshScope,通过注入配置类间接获取动态值。
@Component
@RefreshScope
public class AppConfig {
@Value("${app.config.value}")
private String configValue;
public String getConfigValue() {
return configValue;
}
}
@Component
public class ScheduleTask {
@Autowired
private AppConfig appConfig; // 注入的是代理,每次调用都拿最新值
@Scheduled(cron = "*/5 * * * * ?")
public void execute() {
System.out.println("定时任务执行,配置值:" + appConfig.getConfigValue());
}
}
这种方式的优点是结构清晰,定时任务类不会被销毁重建,任务调度不中断;@RefreshScope 只作用于配置类,刷新时只影响配置值的读取,不影响任务注册关系。
方案二:实现 ApplicationListener<RefreshScopeRefreshedEvent> 接口
如果确实需要在同一个 Bean 中同时使用 @RefreshScope 和 @Scheduled,可以让该 Bean 实现 ApplicationListener<RefreshScopeRefreshedEvent> 接口:
@Component
@RefreshScope
public class RefreshScopeTask implements ApplicationListener<RefreshScopeRefreshedEvent> {
@Value("${app.config.value}")
private String configValue;
@Scheduled(cron = "*/5 * * * * ?")
public void execute() {
System.out.println("定时任务执行,配置值:" + configValue);
}
@Override
public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
// 方法体可以为空
}
}
关键在于 implements ApplicationListener<RefreshScopeRefreshedEvent> 这个接口声明本身。配置刷新完成后,Spring 会发布 RefreshScopeRefreshedEvent 事件,并调用监听器的 onApplicationEvent 方法。由于该 Bean 是 @RefreshScope,Spring 从容器中拿到的是代理对象,调用代理方法会触发懒加载重建真实实例,新实例在初始化过程中 @Scheduled 任务会被重新注册。
参考文档
来源:http://www.cnblogs.com/Crazy_Joker
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

浙公网安备 33010602011771号