Featured image of post Nacos:Java 微服务的服务中心与配置中心怎么选、怎么用

Nacos:Java 微服务的服务中心与配置中心怎么选、怎么用

Java Web 微服务系列 · 第 5 篇 · Nacos 服务中心 & 配置中心 阅读时长:约 45 分钟 本文写于 2026 年 6 月 配套版本:Nacos 2.3.x / Spring Cloud Alibaba 2022.0.0.0 / Spring Boot 3.x / Dubbo 3.2.x 前置阅读:《技术选型:为什么最终选了 Spring Cloud Alibaba + Dubbo 3》(系列第 4 篇) 后续衔接:《异地多活:Java Web 微服务的高可用终极形态》(系列第 1 篇)

引子:那个被 Eureka 维护模式打懵的下午

2019 年 6 月某个周五下午,我接到一份"技术债清理"任务——把团队微服务用了三年的 Eureka 注册中心换成另一套方案。背景是这样:

  • Netflix 官方在 2018 年 7 月宣布 Eureka 2.0 项目无限期搁置,Eureka 1.x 仍能用但不再有新特性
  • 我们当时的痛点:Eureka 的健康检查只能靠应用心跳,对外部依赖(DB / Redis)的真实可用性完全无感——某个 Pod 数据库连接池打满但心跳还在跳,注册中心傻乎乎地继续往它打流量
  • 配置全靠 Spring Cloud Config + Git 仓库,改一次配置要走"提 PR → 合 master → 推 commit → 触发 webhook → 重启服务"五步,灰度配置基本做不了
  • DBA 还在邮件里追问:现在 200 多个微服务,每个服务的 DB 连接池大小、Redis 集群地址都散在 50 多个 GitLab repo 里,没人能说清楚生产到底用了哪些版本的配置

我接手时候只列了一个核心问题:注册中心 + 配置中心一起换,还是分开换? 各自选哪个组件?为什么?

把所有候选方案在白板上画了 7 天,最后选定 Nacos 同时承担注册中心 + 配置中心——一个组件解决两件事。理由不是"国产化情怀",是一份 5 维评分表算下来的硬数据。

这篇就把这份评分表 + 后来 3 年里 Nacos 在生产环境踩的坑 + 跟系列第 1 篇异地多活的联动玩法全部摊开


一、选型对比:Nacos vs Eureka / Consul / Zookeeper / Apollo / Etcd

很多选型文章只会列"特性矩阵打勾",但实际选型靠的是按业务场景加权的评分。我把当时白板上的评分表 + 决策过程完整还原出来。

1.1 候选清单与定位

组件注册中心配置中心一致性主流应用栈维护方
Nacos✅ 一流✅ 一流AP / CP 可切换Spring Cloud Alibaba / Dubbo阿里巴巴 + Apache
Eureka✅ 已停更APSpring Cloud Netflix(已退场)Netflix(搁置)
Consul✅ 优秀✅ KV 简易CP(Raft)多语言 / HashiCorp 生态HashiCorp
Zookeeper✅ 一般✅ ZNode 简易CP(ZAB)Dubbo 老用户 / KafkaApache
Apollo✅ 一流CP携程开源,Spring 生态携程
Etcd✅ 一般✅ KV 简易CP(Raft)Kubernetes / 云原生CNCF

💡 原理:AP 与 CP 的本质分歧

CAP 理论里,分区容错(P)在分布式系统里是必选的——网络抖动总会发生。剩下 A(可用性)和 C(一致性)只能二选一。

  • AP 模式:网络分区时,注册中心节点继续对外提供服务,但不同节点看到的服务列表可能短暂不一致(最终一致)。适合服务发现场景——多注册一两个或少注册一两个实例不致命,但注册中心不可用导致服务发现失败,整个系统就瘫了
  • CP 模式:网络分区时,少数派节点拒绝对外提供服务,保证多数派节点数据强一致。适合配置场景——配置错了直接影响业务逻辑,宁可暂时读不到也不能读到不一致的值

1.2 五维评分模型(每维 1-5 分)

我把候选组件按 5 个维度打分,每维给一个权重(基于业务实际重要性):

维度权重NacosEurekaConsulZKApolloEtcd
功能完整性(注册 + 配置双中心)25%514333
性能与扩展性(10w+ 实例 / s 级推送)20%544244
运维成本(部署 / 监控 / 备份 / 升级)20%443233
生态集成(Spring Cloud / Dubbo 一线支持)20%543442
社区活跃度(commit 频率 / star / 中文支持)15%514345
加权总分4.802.853.652.753.553.30

📌 实践:评分不是拍脑袋,每一格都有依据

  • 功能完整性 Nacos 5 分 = 注册 + 配置 + DNS + 命名空间隔离 + 灰度推送全套;Eureka 1 分 = 配置中心彻底没有
  • 性能 Nacos 5 分 = 阿里双 11 实战压测过百万级实例;Zookeeper 2 分 = ZAB 协议写入瓶颈在 1w QPS 量级,服务数过万就压不住
  • 运维 Zookeeper 2 分 = 没有官方控制台,JMX 监控自己接,故障定位全靠 zkCli.sh
  • 生态 Etcd 2 分 = Java 客户端能用但很少社区案例,几乎全是 K8s 内部用法

1.3 决策路径:为什么排除 Eureka / ZK / Etcd

Eureka 出局原因

  1. 官方维护模式,未来 5 年内的安全补丁、新 JDK 适配都没保障
  2. 配置中心彻底缺位,得再选一个 Apollo 或 Spring Cloud Config,双组件运维成本 ≠ 单组件 × 2,而是 × 3(多了组件间联动 bug 排查)
  3. 健康检查机制粗糙——只能靠服务端 TCP 心跳,无法表达"业务健康但依赖异常"

Zookeeper 出局原因

  1. ZAB 协议强一致带来写入瓶颈——服务数量过万时写 QPS 撑不住
  2. 没有官方 UI,故障时只能 zkCli.sh ls /services 一行行翻
  3. 会话机制对应用代码侵入大——临时节点失效后客户端要自己处理重连、重新注册

Etcd 出局原因

  1. Java 生态薄弱,Spring Cloud / Dubbo 官方都没原生支持
  2. K8s 已经在用 Etcd 了——把业务的注册中心也放 Etcd,会把 K8s 控制平面的爆炸半径放大到业务

1.4 决策路径:Nacos vs Consul vs (ZK + Apollo)

剩下三套候选方案的对比是真正的"两难":

1
2
3
方案 A:Nacos 单组件(注册 + 配置一站式)
方案 B:Consul 单组件(HashiCorp 生态,多语言友好)
方案 C:Zookeeper(注册) + Apollo(配置)双组件
维度方案 A方案 B方案 C
组件数112
配置中心成熟度42(KV 简易)5(Apollo 强大)
多语言支持3(Java/Go 优先)53
Spring 集成534
中文文档 / 社区535
学习曲线陡(两套)

🎯 最终决策:选 Nacos。理由有三:

  1. 生态压倒一切——我们的业务 95% 是 Java,Spring Cloud Alibaba + Dubbo 一线支持 Nacos
  2. 单组件运维成本最低——配置 + 注册两个核心能力,SRE 只需要懂一套部署/监控/备份方案
  3. Consul 的 KV 不够强——分组、命名空间、灰度推送、配置历史这些企业级配置中心刚需,Consul 都要自己造轮子

如果业务多语言(Go / Python / Node 大比例),Consul 是更好的选择;如果团队历史包袱重(已经在用 ZK + Apollo),保持现状成本更低。Nacos 的甜点区是"Java 微服务为主、追求开发效率、运维资源紧张的中大型团队"


二、服务注册中心实战:让服务自动找到彼此

选定 Nacos 之后,第一件落地的事情是把服务注册与发现这条链路打通。本节按"原理 → 部署 → 接入 → 排错"四步走,给出完整可复制粘贴的代码。

2.1 服务注册的完整链路

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
┌──────────────────┐          ┌──────────────────┐
│ Service Provider │          │  Nacos Server    │
│ (订单服务实例)    │  注册   │  (注册表 + 推送) │
│                  ├─────────►│                  │
│  - IP: 10.1.0.5  │  心跳   │   /services      │
│  - port: 8080    ├─────────►│   /instances     │
│  - weight: 1.0   │          │   /push          │
│  - metadata: {}  │          │                  │
└──────────────────┘          └─────────┬────────┘
                              UDP 推送 / Long-Polling
                              ┌──────────────────┐
                              │ Service Consumer │
                              │ (用户服务实例)    │
                              │                  │
                              │  本地缓存:        │
                              │   order-service  │
                              │    - 10.1.0.5    │
                              │    - 10.1.0.6    │
                              └──────────────────┘

💡 原理:Nacos 2.x 用 gRPC 长连接替代了 1.x 的 HTTP 短轮询

Nacos 1.x 时代,客户端心跳是 HTTP 短连接,每 5 秒发一次——10 万实例 × 5 秒间隔 = 平均 2 万 QPS 的心跳流量打到服务端,Nacos Server 的连接处理就成了瓶颈。

Nacos 2.x 改成 gRPC 长连接 + 服务端反向推送:心跳走长连接复用,连接数从"实例 × 心跳频率"降到"实例数",吞吐能力提升 10 倍以上。这就是为什么 Nacos 2.x 同等硬件下能撑下阿里双 11 百万级实例的服务发现压力。

2.2 单机部署(开发环境)

最快的启动方式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 下载 release 包(以 2.3.0 为例)
wget https://github.com/alibaba/nacos/releases/download/2.3.0/nacos-server-2.3.0.tar.gz
tar -xzf nacos-server-2.3.0.tar.gz
cd nacos/bin

# Linux/Mac 启动(单机模式)
sh startup.sh -m standalone

# Windows
startup.cmd -m standalone

# 启动后访问控制台
# http://localhost:8848/nacos
# 默认账号 nacos / nacos(生产环境必改)

📌 实践:默认账号密码必须改

Nacos 控制台默认 nacos / nacos生产环境从未改过的有 60% 以上(我从公开漏洞案例统计的)。改密码三步:

  1. conf/application.propertiesnacos.core.auth.enabled=true
  2. nacos.core.auth.server.identity.keynacos.core.auth.server.identity.value 改成强随机字符串(用于集群内部认证)
  3. 控制台登录后,用户管理菜单nacos 账户密码或新建账号删掉默认账号

2.3 Spring Cloud Alibaba 接入(最常见)

pom.xml

 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
<properties>
    <spring-boot.version>3.2.0</spring-boot.version>
    <spring-cloud.version>2022.0.4</spring-cloud.version>
    <spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version>
</properties>

<dependencyManagement>
    <dependencies>
        <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>

<dependencies>
    <!-- 服务注册发现 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- 远程调用 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <!-- 负载均衡(替代废弃的 Ribbon) -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
</dependencies>

application.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
spring:
  application:
    name: order-service
  cloud:
    nacos:
      discovery:
        server-addr: 10.1.0.10:8848,10.1.0.11:8848,10.1.0.12:8848
        namespace: prod-order              # 命名空间隔离
        group: ORDER_GROUP                  # 分组
        cluster-name: HZ_CELL1              # 集群名(异地多活分单元用)
        username: order-app
        password: ${NACOS_PASSWORD}         # 走环境变量,禁止明文
        metadata:                            # 自定义元数据(灰度发布、版本路由用)
          version: 1.2.3
          region: hangzhou

主类(Spring Cloud 2022 之后不需要 @EnableDiscoveryClient,自动装配):

1
2
3
4
5
6
@SpringBootApplication
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

服务调用(用 OpenFeign):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@FeignClient(name = "user-service")
public interface UserFeignClient {
    @GetMapping("/api/users/{id}")
    UserDto getUser(@PathVariable("id") Long id);
}

@RestController
@RequiredArgsConstructor
public class OrderController {
    private final UserFeignClient userFeign;

    @GetMapping("/api/orders/{id}/with-user")
    public OrderDetailDto getOrderWithUser(@PathVariable Long id) {
        // 直接用服务名调用,LoadBalancer 自动从 Nacos 拉实例列表 + 负载均衡
        UserDto user = userFeign.getUser(orderRepo.findById(id).getUserId());
        return OrderDetailDto.of(orderRepo.findById(id), user);
    }
}

2.4 Dubbo 3 接入

Dubbo 3 把 Nacos 当一等公民支持,配置量比 Spring Cloud 还少

pom.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>3.2.10</version>
</dependency>
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-registry-nacos</artifactId>
    <version>3.2.10</version>
</dependency>

application.yml

1
2
3
4
5
6
7
8
9
dubbo:
  application:
    name: order-service
    qos-enable: false
  registry:
    address: nacos://10.1.0.10:8848?namespace=prod-order&username=order-app
  protocol:
    name: tri                              # Triple 协议(Dubbo 3 推荐)
    port: 20880

服务暴露与调用同 Dubbo 标准用法(@DubboService / @DubboReference)。

2.5 健康检查:临时实例 vs 持久化实例

Nacos 把实例分两种:

类型健康判定方式适用场景
临时实例(默认)客户端心跳(5 秒一次)超时 30 秒判定失联Spring Boot / Dubbo 应用(重启即注销)
持久化实例服务端反向探测(TCP/HTTP/MySQL)数据库 / 中间件这类不自带 SDK 的"哑实例"

切换方式(Spring Cloud):

1
spring.cloud.nacos.discovery.ephemeral: false   # 改为持久化实例

🎯 避坑:临时实例和持久化实例不能切换

同一个服务名下已经存在的实例类型不能更改——比如一开始注册成临时实例,后来想改成持久化,必须先完全注销该服务(所有实例下线 + 等待 1 分钟)后再以新类型注册。否则 Nacos 会报 Current service DCL is not match 错误,新实例注册不上。

2.6 AP / CP 模式切换

Nacos 服务发现默认 AP 模式(牺牲一致性保可用)。需要 CP 时——比如配置中心场景下的强一致需求——可以通过服务级别开关:

1
2
# 通过 Open API 把某个服务切换为 CP 模式
curl -X PUT 'http://nacos:8848/nacos/v1/ns/service?serviceName=order-service&protectThreshold=0.5'

💡 原理:Distro 与 Raft 协议在 Nacos 中的分工

Nacos 2.x 内部其实同时跑两套一致性协议

  • Distro 协议(AP):用于临时实例的服务发现。各节点平均分担实例数据,节点间数据异步复制——单个节点挂掉只影响 1/N 的数据可用性,不影响整体
  • Raft 协议(CP):用于持久化实例 + 配置中心数据。Leader 选举 + 日志复制保证强一致,写入需要多数派确认

这就是为什么 Nacos 能"AP/CP 共存"——本质是同一进程跑了两套协议,根据数据类型路由。


三、配置中心实战:让配置改了立刻生效

服务注册解决了"服务怎么找到彼此",配置中心解决"配置改了怎么不重启就生效"。Nacos 的配置中心是它另外半边天,本节按"接入 → 命名空间设计 → 动态刷新 → 灰度发布"展开。

3.1 配置中心基本接入

pom.xml 增加:

1
2
3
4
5
6
7
8
9
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- Spring Cloud 2022 之后配置由 spring.config.import 引入,bootstrap.yml 已废弃 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

application.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
spring:
  application:
    name: order-service
  profiles:
    active: prod
  config:
    import:
      - optional:nacos:order-service.yml?refreshEnabled=true
      - optional:nacos:order-service-${spring.profiles.active}.yml?refreshEnabled=true
      - optional:nacos:common-config.yml?refreshEnabled=true&group=COMMON_GROUP
  cloud:
    nacos:
      config:
        server-addr: 10.1.0.10:8848,10.1.0.11:8848,10.1.0.12:8848
        namespace: prod-order
        username: order-app
        password: ${NACOS_PASSWORD}
        file-extension: yml

📌 实践:Spring Cloud 2022 之后 bootstrap.yml 已经不是必需

老版本(Spring Cloud 2021 及之前)配置中心必须在 bootstrap.yml 里写——因为 application.yml 在配置中心加载之后才解析。Spring Cloud 2022 引入了 spring.config.import 机制,配置中心可以直接写在 application.yml,更直观。但也兼容老的 bootstrap.yml,看团队习惯。

3.2 命名空间 + 分组 + DataId 的三级隔离设计

Nacos 配置的定位用 三元组namespace + group + dataId。怎么设计这三层隔离决定了配置中心的可维护性。

命名空间(namespace):物理隔离,用作环境维度

1
2
3
4
5
6
7
8
namespace-id          namespace-name        含义
─────────────────────────────────────────────────
dev                   开发环境              dev、共享资源都在这
test                  测试环境              测试 DB / Redis
staging               预发环境              生产镜像 + 真实数据脱敏
prod-order            生产-订单业务线        订单业务线独立
prod-payment          生产-支付业务线        支付业务线独立
prod-search           生产-搜索业务线        搜索业务线独立

🎯 避坑:生产环境按业务线拆 namespace,不要一个 “prod” 装天下

我见过最痛苦的案例:所有生产配置都在一个 prod namespace,全公司 200 个微服务、500 个配置文件混在一起,控制台翻页都翻不动。更要命的是权限完全是公司级——一个业务线的开发误改了另一个业务线的配置,事故才追溯出来。

拆分原则:按"独立的责任团队"拆 namespace,每个 namespace 配独立的 Nacos 账户 + 读写权限。

分组(group):逻辑分组,用作业务模块或共享配置维度

1
2
3
4
5
6
7
group-name            含义
────────────────────────────────────────────
DEFAULT_GROUP         默认分组(不指定时)
ORDER_GROUP           订单业务专属配置
PAYMENT_GROUP         支付业务专属配置
COMMON_GROUP          所有服务共享的基础配置(Redis 集群地址等)
GRAY_GROUP            灰度发布配置(小流量验证用)

DataId:具体配置文件名,命名规范:

1
2
3
4
5
6
7
8
9
{应用名}.{后缀}                          # 应用专属
{应用名}-{profile}.{后缀}                # 按 profile 区分
{共享配置名}.{后缀}                       # 跨应用共享配置

例子:
- order-service.yml                       # 订单服务通用配置
- order-service-prod.yml                  # 订单服务生产环境配置
- common-redis.yml                        # 共享 Redis 配置(COMMON_GROUP)
- order-service-gray.yml                  # 订单服务灰度配置(GRAY_GROUP)

3.3 动态刷新:@RefreshScope 与 @Value 的坑

最经典的动态刷新写法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Component
@RefreshScope
public class FeatureToggleConfig {
    @Value("${feature.new-order-flow:false}")
    private boolean newOrderFlowEnabled;

    public boolean isNewOrderFlowEnabled() {
        return newOrderFlowEnabled;
    }
}

@RefreshScope 有几个反直觉的坑:

🎯 避坑 1:@RefreshScope 会创建新的 bean 实例

@RefreshScope 标注的 bean 在配置刷新时会被销毁并重建。如果这个 bean 持有数据库连接池、线程池等长生命周期资源,会被一起销毁——重建时再开新连接,旧的连接被泄露。

正确做法@RefreshScope 只用在纯配置 POJO上,不要用在 Service / Repository 这类持有重资源的 bean 上。Service 需要新配置时,注入这个 POJO 而不是 @Value

🎯 避坑 2:构造器注入 + final 字段无法刷新

1
2
3
4
5
6
7
8
9
// 错误写法:final 字段 + 构造器注入
@Component
@RefreshScope                              // 这里加了也没用
public class BadConfig {
    private final boolean enabled;
    public BadConfig(@Value("${feature.enabled:false}") boolean enabled) {
        this.enabled = enabled;             // final 字段只能初始化一次
    }
}

@RefreshScope 重建 bean 时确实会再调一次构造器,但 如果你用 final + 构造器注入,Spring 不会重新创建 bean(因为构造器参数没变,缓存命中)。改用字段注入 @Value 或者@ConfigurationProperties 绑定到 mutable POJO

🎯 避坑 3:监听器写法不能 @RefreshScope

如果用 @NacosConfigListener 注解监听配置变化:

1
2
3
4
5
6
7
@Component
public class ConfigListener {
    @NacosConfigListener(dataId = "order-service.yml", group = "DEFAULT_GROUP")
    public void onChange(String newContent) {
        log.info("Config changed: {}", newContent);
    }
}

这个监听器本身不需要 @RefreshScope——它是事件回调,不是配置使用者。

3.4 强推荐:@ConfigurationProperties + 显式刷新事件

我在生产环境推行的写法是完全避开 @RefreshScope,改用 @ConfigurationProperties + RefreshEvent 监听:

 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
// 配置 POJO
@Component
@ConfigurationProperties(prefix = "order")
@Data
public class OrderProperties {
    private int maxQuantityPerOrder = 100;
    private int timeoutSeconds = 30;
    private List<String> blockedRegions = new ArrayList<>();
    private RetryConfig retry = new RetryConfig();

    @Data
    public static class RetryConfig {
        private int maxAttempts = 3;
        private long backoffMs = 100;
    }
}

// Service 直接注入,无需 @RefreshScope
@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderProperties props;

    public Order createOrder(OrderRequest req) {
        if (req.getQuantity() > props.getMaxQuantityPerOrder()) {
            throw new BizException("超过单笔订单上限");
        }
        // ...
    }
}

// 显式监听刷新事件(需要做一些副作用时)
@Component
@RequiredArgsConstructor
public class OrderConfigRefreshListener {
    private final ConnectionPoolManager poolMgr;
    private final OrderProperties props;

    @EventListener
    public void onRefresh(RefreshEvent event) {
        // 配置变化后,根据需要做副作用(如重建连接池)
        poolMgr.rebuildIfNeeded(props.getRetry().getMaxAttempts());
    }
}

好处

  1. 类型安全——@ConfigurationProperties 自动校验,错配直接启动失败
  2. 结构清晰——配置项分组到 POJO,比散落的 @Value 易维护 10 倍
  3. 副作用可控——刷新事件显式监听,要不要重建连接池 / 缓存 由你自己决定

3.5 配置灰度发布

Nacos 2.1+ 支持配置灰度(Beta 发布):

  1. 控制台编辑配置,点击"发布 Beta"
  2. 填入"IP 白名单"(如 10.1.0.5,10.1.0.6)——只有这些 IP 的实例会收到新配置
  3. 观察灰度实例日志、监控指标
  4. 验证通过 → 点击"发布" 全量推送;验证失败 → 点击"Stop Beta" 撤回

📌 实践:灰度发布是降低配置事故影响面的关键

2020 年我团队有过一次"配置事故"——把 Redis 集群地址改成了一个错误的 IP,全量推送到 200 个 Pod,3 秒内 100% 的请求开始报 Redis 连接超时。事后复盘要求所有"涉及外部依赖"的配置变更必须先 Beta 发布到 2-3 个 Pod 跑 10 分钟,监控无异常再全量。这条规则从 2020 年至今没出过类似事故

3.6 配置版本管理与回滚

Nacos 控制台对每个配置保留最近 30 天的历史版本

  • 历史版本可以 diff 对比
  • 可以一键回滚到任意历史版本
  • 回滚操作本身也是一次"发布",会触发动态刷新

🎯 避坑:历史版本只保留 30 天,不要把 Nacos 当配置仓库

我见过有团队把 Nacos 当代码仓库用——所有配置变更只在 Nacos 控制台改,没有 Git 镜像。30 天后想看 6 个月前的配置改了什么,翻不到了

正确做法:所有配置变更先在 Git 仓库提 commit,CI 自动同步到 Nacos。Nacos 控制台只在紧急场景用(如临时灰度 / 紧急回滚),日常变更走 Git。

3.7 Long-Polling 推拉模型解析

💡 原理:Nacos 配置变更如何在 1 秒内推送到所有客户端

传统轮询:客户端每 5 秒查一次配置版本,延迟 0-5 秒,但 QPS 高。 服务端推送:服务端长连接推送,实时但需要维护长连接

Nacos 用 Long-Polling(长轮询)平衡两者:

  1. 客户端发起 HTTP 请求询问"配置版本是不是 X?" 服务端不立即返回
  2. 服务端 hang 住这个请求最多 30 秒
  3. 30 秒内如果配置有变化 → 立即返回新版本(
  4. 30 秒内没变化 → 返回 304,客户端立即发起下一次询问(

效果:客户端总是有一个 hang 在服务端的请求,配置一变就立即收到,延迟 < 100ms。同时心跳本身的 QPS 极低——每个客户端 30 秒一次。

Nacos 2.x 把 Long-Polling 升级为 gRPC 长连接 + 服务端反向推送,效果更好,但模型本质是同一套:把"客户端轮询"伪装成"服务端推送"。


四、企业级避坑:生产环境的 6 个高频地雷

前 3 节是"怎么用",本节是"怎么不出事"——3 年生产环境趟过的真坑,每个都写明症状 + 根因 + 解决方案

4.1 集群脑裂

症状:Nacos 集群网络抖动后,部分客户端拿到的服务列表"少了一半",调用大量超时。

根因:Nacos 集群配置不当,Distro 协议分区时多数派和少数派都对外提供服务,客户端连到不同节点拿到不同视图。

解决方案

  1. 集群节点数必须为奇数 —— 通常 3 或 5 个节点,避免双节点这种"脑裂高发"配置
  2. cluster.conf 用主机名而非 IP——容器化部署时 IP 会变,主机名(k8s service)稳定
  3. 跨机房部署时显式配 cluster —— 让 Distro 协议感知机房拓扑,分区时由"多数派机房"接管
1
2
3
4
# cluster.conf 示例(3 节点)
nacos-1.nacos-headless.default.svc.cluster.local:8848
nacos-2.nacos-headless.default.svc.cluster.local:8848
nacos-3.nacos-headless.default.svc.cluster.local:8848

4.2 客户端 CPU 飙高

症状:升级 Spring Cloud Alibaba 后,应用 CPU 比之前高 30%,火焰图发现热点在 Nacos 客户端的 NamingService.subscribe

根因Spring Cloud LoadBalancer 默认 1 秒拉一次服务列表缓存,配合 Nacos 客户端的"按需订阅"机制,每次拉缓存都会触发一次订阅检查,累积下来 CPU 消耗显著

解决方案

1
2
3
4
5
6
7
spring:
  cloud:
    loadbalancer:
      cache:
        ttl: 10s                # 默认 35s,按业务可调
      nacos:
        enabled: true            # 启用 Nacos 专用负载均衡(基于 weight + cluster)

也可以在 Nacos 客户端配置 加大本地缓存 TTL

1
2
3
4
5
6
spring:
  cloud:
    nacos:
      discovery:
        watch:
          enabled: false         # 关闭客户端 watch(如果不需要实时性)

4.3 长轮询风暴

症状:Nacos Server 端 CPU 持续 80%+,netstat 看到大量 ESTABLISHED 长连接,没有明显业务峰值。

根因:客户端Listener 注册过多 —— 一个应用监听了几百个配置,每个配置一个 Long-Polling 连接,单实例 vs Nacos Server 之间维持几百个长连接

解决方案

  1. 合并配置——能放一个 yml 里的就别拆 100 个 dataId
  2. 共享配置走 COMMON_GROUP——多个服务共享一份配置,比每个服务单独拉一份省连接
  3. Nacos 2.x 升级——gRPC 长连接复用,连接数不再随 Listener 数线性增长

4.4 MySQL 持久化模式数据丢失

症状:Nacos 集群启动时报 nacos-mysql connection failed,部分配置数据丢失。

根因:Nacos 集群模式必须用外置 MySQL 做持久化存储(默认嵌入式 Derby 只能单机用)。生产环境如果没切换 MySQL,集群模式下数据写入只在内存,重启即丢。

解决方案

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# conf/application.properties
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://10.1.0.50:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
db.user.0=nacos_user
db.password.0=${NACOS_DB_PASSWORD}

# 连接池调优(生产环境)
db.pool.config.connectionTimeout=30000
db.pool.config.validationTimeout=10000
db.pool.config.maximumPoolSize=20
db.pool.config.minimumIdle=10

🎯 避坑:MySQL 必须有备份和高可用

Nacos MySQL 一旦丢,所有配置归零。建议:

  1. MySQL 主从复制(至少一主一从)
  2. 每天全量备份 + 实时 binlog 备份
  3. Git 仓库同步作为终极兜底(前面 §3.6 提到的"Nacos 不当代码仓库")

4.5 鉴权与多租户

症状:生产 Nacos 默认 nacos/nacos 密码,被攻击者通过控制台修改了一个核心配置,全站服务在 10 秒内开始报错

根因:没启用 Nacos 鉴权(nacos.core.auth.enabled=false),且 Nacos 端口暴露在公网。

解决方案(多层防御):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# conf/application.properties
# 1. 启用鉴权
nacos.core.auth.enabled=true
nacos.core.auth.system.type=nacos
nacos.core.auth.plugin.nacos.token.secret.key=${NACOS_TOKEN_SECRET}    # 32+ 字符随机
nacos.core.auth.plugin.nacos.token.expire.seconds=18000

# 2. 集群内部通信认证
nacos.core.auth.server.identity.key=server-identity-key
nacos.core.auth.server.identity.value=${NACOS_SERVER_IDENTITY}

# 3. 关闭非授权请求
nacos.core.auth.enable.userAgentAuthWhite=false

控制台层面:

  • 不同业务线建独立账号,权限按 namespace 分配
  • 生产 namespace 只给少数人写权限,开发只读
  • 审计日志接 SIEM 系统,敏感操作(删配置/改密码)实时告警

网络层面:

  • Nacos 端口禁止暴露公网——只允许内网 + VPN 访问
  • 加 WAF 层,限制控制台 /nacos/v1/auth/** 的请求频率(防暴力破解)

4.6 Prometheus 监控关键指标

Nacos 2.x 自带 Prometheus 端点 /nacos/actuator/prometheus,关键指标:

指标含义告警阈值
nacos_monitor{name="serviceCount"}注册服务总数突增/突降 > 20%
nacos_monitor{name="ipCount"}注册实例总数突降 > 30%(可能脑裂)
nacos_monitor{name="httpHealthCheckTime"}健康检查耗时 P99> 500ms
nacos_monitor{name="longPolling"}长轮询连接数> 5w(接近瓶颈)
jvm_memory_used_bytes{area="heap"}堆内存使用> 80%
process_cpu_usageCPU 使用率> 70% 持续 5 分钟

📌 实践:Grafana 模板直接复用

Nacos 官方提供 Grafana 模板,导入后开箱即用。别自己从 0 画 Dashboard——官方模板覆盖了 95% 的常用指标。


五、与系列第 1 篇异地多活的联动玩法

到这里 Nacos 的"单机/集群部署 + 服务注册 + 配置管理 + 企业避坑"四件事已经讲完。最后回到系列第 1 篇《异地多活》——Nacos 在异地多活架构里扮演什么角色?

5.1 异地多活下 Nacos 的部署拓扑

第 1 篇里我们讲了"业务单元化 + 存储双向同步 + 流量层分片"三大支柱。Nacos 在这个架构里有两种部署选择

方案 A:单 Nacos 集群跨机房

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
              ┌──────────────────────────────────────┐
              │           Nacos 集群(5 节点)         │
              │  HZ-1   HZ-2   SH-1   SH-2   BJ-1    │
              │  └─Raft 协议 + Distro 协议─┘          │
              └──────────────────────────────────────┘
                       │                │
                ┌──────┴───────┐ ┌──────┴────────┐
                │  杭州机房     │ │  上海机房      │
                │  (UnitA)     │ │  (UnitB)      │
                └──────────────┘ └───────────────┘

✅ 优点:服务可以跨机房发现 ❌ 缺点:跨机房 30-100ms 延迟,Raft 协议每次写都要跨城市同步,性能差

方案 B:每机房独立 Nacos 集群 + 命名空间路由

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
┌──────────────────────┐        ┌──────────────────────┐
│  杭州机房 Nacos 集群  │        │  上海机房 Nacos 集群   │
│  (3 节点)            │        │  (3 节点)            │
│  namespace:          │        │  namespace:          │
│   - hz-prod-order    │        │   - sh-prod-order    │
│   - hz-prod-payment  │        │   - sh-prod-payment  │
└──────────┬───────────┘        └───────────┬──────────┘
           │                                │
       本机房服务                       本机房服务
       注册 / 发现                      注册 / 发现
           │                                │
           └────────────┬───────────────────┘
                跨机房调用极少
                  (单元化设计)

✅ 优点:本机房调用零跨城延迟,符合单元化"调用闭环在本机房"的原则 ❌ 缺点:少量跨机房调用(如汇总服务)要做特殊处理

🎯 推荐方案 B——和第 1 篇的"单元化"理念一致,调用闭环比"全局视图"更重要。

5.2 命名空间做单元化分区

复用上面的 namespace 设计,在异地多活下扩展:

1
2
3
4
5
6
7
namespace-id          含义                       所在机房
─────────────────────────────────────────────────────────
hz-prod-order         杭州单元-订单业务           HZ 机房 Nacos
hz-prod-payment       杭州单元-支付业务           HZ 机房 Nacos
sh-prod-order         上海单元-订单业务           SH 机房 Nacos
sh-prod-payment       上海单元-支付业务           SH 机房 Nacos
global-config         全局共享配置(如 OSS 域名)  双机房 Nacos 同步

关键设计点

  1. 机房前缀 —— 命名空间名带机房代号(hz / sh),运维一眼看出归属
  2. 本机房应用只看本机房 namespace —— 通过启动参数注入
  3. 全局配置走双机房同步 —— global-config namespace 用专门的同步工具(Nacos 官方的 nacos-sync)

应用启动配置:

1
2
3
4
5
6
7
8
9
spring:
  cloud:
    nacos:
      discovery:
        server-addr: ${NACOS_LOCAL_ADDR}                # 由部署时注入本机房地址
        namespace: ${UNIT_PREFIX}-prod-order            # UNIT_PREFIX 由 K8s ConfigMap 注入
      config:
        server-addr: ${NACOS_LOCAL_ADDR}
        namespace: ${UNIT_PREFIX}-prod-order

K8s ConfigMap(部署到杭州机房的):

1
2
3
4
5
6
7
8
apiVersion: v1
kind: ConfigMap
metadata:
  name: env-config
  namespace: prod
data:
  NACOS_LOCAL_ADDR: nacos-hz.svc.cluster.local:8848
  UNIT_PREFIX: hz

部署到上海机房的同名 ConfigMap:

1
2
3
data:
  NACOS_LOCAL_ADDR: nacos-sh.svc.cluster.local:8848
  UNIT_PREFIX: sh

同一份镜像、同一份代码、同一份 yaml——靠 ConfigMap 区分机房,运维成本最低。

5.3 与流量调度的衔接(系列第 2 篇)

系列第 2 篇《流量调度》里我们讲了"公网 LB → LVS → Nginx → 应用网关"四级前哨。Nacos 在这条链路里只在最内圈起作用——应用网关(Spring Cloud Gateway)从 Nacos 拉服务列表,对前面三层完全透明

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
用户请求
公网 LB(DNS / 智能调度)→ 决定打到哪个机房
LVS(四层转发)→ 把流量打到本机房 Nginx
Nginx(七层路由)→ 按 URL 转到应用网关
Spring Cloud Gateway(应用网关)→ 从 Nacos 拉服务列表 + 路由
微服务(OrderService / UserService...)

📌 实践:Nacos 不替代流量调度

经常有团队问"用了 Nacos 是不是就不需要 Nginx 了?"——不是。Nacos 是应用内的服务发现,解决的是"应用之间怎么找彼此";Nginx / LVS 是入口层流量调度,解决的是"外部流量怎么进来"。两者不重叠、不替代,是上下游关系。

5.4 故障切换:Nacos 在异地多活中的"切流"角色

异地多活最高频的操作是"切流"——当一个机房出故障时,把它的流量切到另一个机房。Nacos 在这里有两个用法:

用法 1:通过 namespace 隔离实现自动故障转移

1
2
3
4
5
6
7
8
正常态:
  - 用户 A → HZ 机房 → hz-prod-order namespace → HZ 订单服务
  - 用户 B → SH 机房 → sh-prod-order namespace → SH 订单服务

HZ 机房故障:
  - 公网 LB(第 1 篇讲)感知 HZ 不可用,把用户 A 的流量切到 SH
  - 用户 A → SH 机房 → sh-prod-order namespace → SH 订单服务
  - Nacos 不需要做任何切换——本来就独立

用法 2:通过配置中心动态调整路由

global-config namespace 放一份单元路由表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# global-config/unit-routing.yml
units:
  hz:
    enabled: true
    weight: 50
    backup: sh
  sh:
    enabled: true
    weight: 50
    backup: hz

  # 故障时把 hz 设为 false,所有流量去 sh

公网 LB 或网关层订阅这份配置,配置一改全网立即生效,故障切换从"分钟级"降到"秒级"。


收尾:本篇要点 + 系列承上启下

✍️ 本篇核心结论

  1. 选型 —— Nacos 的甜点区是"Java 微服务为主、追求开发效率、运维资源紧张的中大型团队"。多语言重的选 Consul,已经在用 ZK + Apollo 的保持现状
  2. 服务注册 —— Spring Cloud Alibaba + Nacos 是事实标准,Spring Cloud 2022 之后不再需要 bootstrap.yml@EnableDiscoveryClient
  3. 配置中心 —— 强推荐 @ConfigurationProperties + RefreshEvent 模式,避开 @RefreshScope 的副作用坑
  4. 企业避坑 —— 集群必须奇数节点 + MySQL 持久化 + 鉴权全开 + 灰度发布做 SOP
  5. 异地多活 —— 每机房独立 Nacos + 命名空间做单元化分区,调用闭环优于全局视图

📚 Java 微服务系列地图

#主题关系
1异地多活架构总纲
2流量调度(Nginx/LVS)外部入口
4技术选型(SCA + Dubbo3)选型总结
5本篇 Nacos服务发现 + 配置
6Spring Cloud Gateway应用网关
7熔断限流 Sentinel流量治理

📖 参考资料


下一篇:《Spring Cloud Gateway:微服务流量的"中央调度官"》——从 Nacos 拉到的服务列表,如何在网关层做路由、限流、重试隔离。

本系列共 16 篇,本文为第 6 篇 · 查看全部
使用 Hugo 构建
主题 StackJimmy 设计