在微服务架构中,你会频繁听到 Nacos 这个名字。它到底是什么?为什么几乎所有 Spring Cloud Alibaba 的项目都在用它?这篇文章从"它是什么"讲到"怎么用",帮你彻底搞懂 Nacos 在微服务中扮演的角色。
目录
- [1. Nacos 是什么------一句话定义](#1. Nacos 是什么——一句话定义)
- [2. 为什么需要 Nacos------它解决了什么问题](#2. 为什么需要 Nacos——它解决了什么问题)
- [3. Nacos 的两大核心功能](#3. Nacos 的两大核心功能)
- [4. Nacos 的定位------和其他工具的对比](#4. Nacos 的定位——和其他工具的对比)
- [5. 核心概念------六个必须懂的术语](#5. 核心概念——六个必须懂的术语)
- [6. 快速上手:搭建 Nacos 服务端](#6. 快速上手:搭建 Nacos 服务端)
- [7. 实战一:服务注册与发现](#7. 实战一:服务注册与发现)
- [8. 实战二:配置中心](#8. 实战二:配置中心)
- [9. 进阶用法](#9. 进阶用法)
- [10. 生产环境部署](#10. 生产环境部署)
- [11. 常见面试题](#11. 常见面试题)
- [12. 总结](#12. 总结)
1. Nacos 是什么------一句话定义
Nacos 是阿里巴巴开源的"服务注册中心 + 配置中心"二合一的中间件。
名字 Nacos 来自:Na ming + Co nfiguration Service,直译就是"命名和配置服务"。这个名字已经把它的两大核心功能说得很清楚了:
- Naming(命名/注册):服务注册与发现
- Configuration(配置):分布式配置管理
简单说:在微服务架构里,服务之间要互相找到对方(注册中心),每个服务要动态读取配置(配置中心),这两件事 Nacos 都帮你搞定。
2. 为什么需要 Nacos------它解决了什么问题
要理解 Nacos 的价值,你得先理解微服务架构面临的两个痛点。
2.1 痛点一:服务之间怎么互相找到对方?
单体应用的时代(传统 Spring MVC):
整个系统就是一个 war 包,所有功能都在同一个进程里。A 方法调 B 方法,就是普通的 Java 方法调用,不存在"找不到对方"的问题。
微服务时代:
系统被拆成几十个甚至上百个独立的服务,每个服务都部署在不同的机器上。比如:
订单服务(部署在 3 台机器:192.168.1.10、1.11、1.12)
用户服务(部署在 2 台机器:192.168.2.20、2.21)
支付服务(部署在 4 台机器:192.168.3.30 ~ 3.33)
问题来了:订单服务要调用用户服务,它怎么知道用户服务在哪台机器上? 总不能在代码里写死 IP 地址吧------机器可能随时增加、减少、宕机、重启。
Nacos 的解决方案:注册中心
每个服务启动时 → 向 Nacos 报告:"我是用户服务,我的地址是 192.168.2.20:8080"
调用方要调用时 → 问 Nacos:"用户服务在哪?"
Nacos 回答 → "在 192.168.2.20:8080 和 192.168.2.21:8080"
调用方从列表中选一个地址调用
这就是服务注册与发现。
2.2 痛点二:配置文件怎么管理?
单体应用的时代:
配置都写在 application.yml 里,改了配置重启应用就生效。简单粗暴但够用。
微服务时代:
假设你有 50 个微服务,每个服务都有自己的配置文件。数据库密码要改?你得改 50 个文件、重启 50 个服务。更糟的是:
- 配置散落各处:不知道哪些服务用了哪些配置
- 不同环境配置不同:dev、test、prod 各一份,复制粘贴很容易出错
- 改配置必须重启:线上改个功能开关就得发版,运维崩溃
Nacos 的解决方案:配置中心
所有服务的配置都放在 Nacos 上 → 统一管理
运维在 Nacos 控制台改配置 → 相关服务自动收到变更
不需要重启服务 → 配置实时生效
这就是分布式配置管理。
2.3 Nacos 一次性解决了这两个问题
很多公司原本用 Eureka + Apollo 或者 Zookeeper + Spring Cloud Config 的组合。Nacos 的最大优势是:把注册中心和配置中心做在一起,一个服务端、一套运维、一个控制台,大幅降低了复杂度。
3. Nacos 的两大核心功能
3.1 功能一:服务注册与发现(Naming)
服务注册与发现流程:
═══════════════════════════════════════
┌─────────────────────────────────────────────────┐
│ Nacos 服务端 │
│ ┌─────────────────────────────────────────┐ │
│ │ 服务注册表 │ │
│ │ ├── user-service → [IP1, IP2] │ │
│ │ ├── order-service → [IP3, IP4, IP5] │ │
│ │ └── pay-service → [IP6, IP7, IP8, IP9] │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
▲ │
│ ① 注册 │ ② 查询
│ ▼
┌─────────┐ ┌──────────────┐
│ 用户服务 │ │ 订单服务 │
│ 启动时 │ │ 调用时 │
│ 上报地址 │ │ 拉取列表 │
└─────────┘ └──────────────┘
关键动作:
- 服务注册:服务启动时,告诉 Nacos "我在这"
- 服务发现:调用方从 Nacos 拉取服务的地址列表
- 心跳检测:服务定期向 Nacos 发心跳,Nacos 剔除挂掉的服务
- 负载均衡:调用方拿到多个地址后,按策略(轮询、随机等)选一个调用
3.2 功能二:配置中心(Configuration)
配置中心工作流程:
═══════════════════════════════════════
运维/开发人员
│
│ ① 在控制台修改配置
▼
┌─────────────────────────┐
│ Nacos 服务端 │
│ ┌─────────────────┐ │
│ │ 配置仓库 │ │
│ │ ├── user-svc.yml │ │
│ │ ├── order-svc.yml│ │
│ │ └── common.yml │ │
│ └─────────────────┘ │
└─────────────────────────┘
│
│ ② 推送变更(长轮询)
▼
┌──────────────────────────────────────┐
│ 应用 A 应用 B 应用 C │
│ 收到变更 收到变更 收到变更 │
│ 自动刷新 Bean 属性 │
└──────────────────────────────────────┘
│
│ ③ 无需重启,配置实时生效
▼
业务继续跑
关键特性:
- 集中管理:所有配置放在 Nacos 上
- 动态推送:配置变更自动推送给订阅的服务
- 版本管理:每次修改都有历史记录,可以一键回滚
- 多环境隔离:dev、test、prod 的配置互不干扰
4. Nacos 的定位------和其他工具的对比
市面上做注册中心和配置中心的工具不止 Nacos 一个。我们来看看它们的区别:
4.1 注册中心对比
| 产品 | 出品方 | 一致性协议 | 健康检查 | 适用场景 |
|---|---|---|---|---|
| Eureka | Netflix(已停止维护) | AP | 客户端心跳 | Spring Cloud 老项目 |
| Zookeeper | Apache | CP | TCP 长连接 | Dubbo、大数据 |
| Consul | HashiCorp | CP | 多种方式 | 多语言、Service Mesh |
| Nacos | 阿里巴巴 | AP/CP 可切换 | 心跳 + TCP | 推荐首选 |
4.2 配置中心对比
| 产品 | 出品方 | 配置动态推送 | 权限管理 | 和注册中心整合 |
|---|---|---|---|---|
| Spring Cloud Config | Spring 官方 | 需配合 Bus | 弱 | 不整合 |
| Apollo | 携程 | ✅ | ✅ 强 | 不整合 |
| Nacos | 阿里巴巴 | ✅ | ✅ | ✅ 二合一 |
4.3 Nacos 的核心优势
一、二合一架构
其他方案你通常需要部署两套中间件(比如 Eureka + Apollo),Nacos 一套搞定。部署、运维、学习成本都降低。
二、AP/CP 可切换
- AP 模式(默认):优先保证服务可用性(临时实例,没心跳就剔除)
- CP 模式:优先保证数据一致性(永久实例,适合数据库主备这种场景)
大多数场景用 AP 模式就够了。
三、阿里巴巴的生产验证
Nacos 源自阿里内部的 ConfigServer(用了 10 多年),双 11 级别流量的实战检验。
四、中文文档和社区
国内开发者最友好------所有文档都有中文,官方群活跃,问题能快速得到回应。
4.4 什么时候选择 Nacos?
- 新项目:直接选 Nacos,没有理由选别的
- Spring Cloud Alibaba 技术栈:Nacos 是标配
- Dubbo 项目:Nacos 也是官方推荐的注册中心
- 已有 Eureka 项目想升级:建议迁移到 Nacos(Eureka 官方已停止维护)
5. 核心概念------六个必须懂的术语
在使用 Nacos 之前,你必须理解这六个概念。它们是 Nacos 所有功能的基础。
5.1 服务(Service)
一个可被调用的业务单元。比如"订单服务"、"用户服务"。
5.2 实例(Instance)
服务的具体部署节点。一个服务可以有多个实例(集群部署)。
订单服务(Service)
├── 实例 1:192.168.1.10:8080
├── 实例 2:192.168.1.11:8080
└── 实例 3:192.168.1.12:8080
5.3 命名空间(Namespace)
用于隔离不同环境的配置和服务。这是 Nacos 最重要的概念之一。
命名空间:dev(开发环境)
├── 服务列表:user-service、order-service...
└── 配置列表:user-service.yml、order-service.yml...
命名空间:test(测试环境)
├── 服务列表:(完全独立的一套)
└── 配置列表:(完全独立的一套)
命名空间:prod(生产环境)
├── 服务列表:(完全独立的一套)
└── 配置列表:(完全独立的一套)
不同命名空间之间完全隔离,服务找不到对方,配置也互不影响。 这样你就可以用一套 Nacos 管理多个环境。
5.4 分组(Group)
在命名空间内部进一步分组。常见的用法:
- 按业务线分组:
ORDER_GROUP、USER_GROUP、PAY_GROUP - 按部门分组:
TEAM_A、TEAM_B
默认分组是 DEFAULT_GROUP。大多数项目用默认分组就够了。
5.5 DataId(配置 ID)
配置的唯一标识,通常是配置文件的名字 ,比如 user-service-dev.yml。
Nacos 通过 DataId + Group + Namespace 这三个维度唯一确定一份配置。
5.6 三者的关系图
Nacos 服务端
├── 命名空间:dev
│ ├── 分组:DEFAULT_GROUP
│ │ ├── DataId:user-service.yml ← 配置文件
│ │ └── DataId:order-service.yml ← 配置文件
│ └── 分组:ORDER_GROUP
│ └── DataId:shared-config.yml
│
├── 命名空间:test
│ └── ...(独立的一套)
│
└── 命名空间:prod
└── ...(独立的一套)
找配置的路径:命名空间 → 分组 → DataId。三者结合才能唯一定位一份配置。
6. 快速上手:搭建 Nacos 服务端
这一节我们从零开始,把 Nacos 服务端跑起来。如果你只是学习/演示,单机模式就够了;生产环境请直接看第 10 章。
6.1 准备工作(环境要求)
启动 Nacos 之前,确认你的环境满足这些条件:
| 依赖 | 要求 | 检查命令 |
|---|---|---|
| JDK | 1.8 或更高 | java -version |
| Maven(可选) | 3.2.x+ | mvn -version |
| 内存 | 至少 2GB 空闲 | --- |
| 端口 | 8848、9848、9849 未被占用 | Windows: `netstat -ano |
踩坑提示 :Nacos 2.x 比 1.x 多用了 9848(gRPC 通信)和 9849(gRPC 服务间同步)两个端口,这两个端口也必须开放,否则客户端连不上。
6.2 下载安装
前往 Nacos GitHub Release 下载最新稳定版(推荐 2.x 版本,本文以 2.3.0 为例)。
Linux / Mac:
bash
# 下载
wget https://github.com/alibaba/nacos/releases/download/2.3.0/nacos-server-2.3.0.tar.gz
# 解压
tar -xvf nacos-server-2.3.0.tar.gz
# 进入目录
cd nacos
Windows: 直接下载 nacos-server-2.3.0.zip,解压到任意目录(注意路径不要有中文和空格,否则启动会报错)。
6.3 解压后的目录结构
了解目录结构能帮你快速定位问题:
nacos/
├── bin/ # 启动脚本
│ ├── startup.sh # Linux/Mac 启动脚本
│ ├── startup.cmd # Windows 启动脚本
│ └── shutdown.sh # 停止脚本
├── conf/ # 配置文件
│ ├── application.properties # 核心配置(端口、数据库、鉴权等)
│ ├── nacos-mysql.sql # MySQL 建表 SQL(生产环境用)
│ └── cluster.conf.example # 集群节点配置模板
├── data/ # 数据目录(默认 Derby 数据库存这里)
├── logs/ # 日志目录(出问题先看这里!)
│ ├── nacos.log # 主日志
│ └── start.out # 启动日志
└── target/ # Nacos 自身的 jar 包
新手必记 :出问题就看 logs/nacos.log 和 logs/start.out。
6.4 启动(单机模式)
Linux / Mac:
bash
# 进入 bin 目录
cd bin
# 单机模式启动
sh startup.sh -m standalone
Windows(重点!):
Windows 上不能直接双击 startup.cmd,因为默认是集群模式启动,会闪退。两种正确方式:
bash
# 方式一:命令行启动(推荐)
startup.cmd -m standalone
# 方式二:修改 startup.cmd 文件
# 找到这一行:set MODE="cluster"
# 改成: set MODE="standalone"
# 然后双击启动
-m standalone 是什么意思?
-m= mode(模式)standalone= 单机模式(不连其他 Nacos 节点)- 不加这个参数 = 默认集群模式(会去找其他节点,找不到就启动失败)
6.5 验证启动成功
启动后看 logs/start.out 最后几行,看到以下内容说明成功:
Nacos started successfully in stand alone mode. use embedded storage
或者用更直接的方式------访问健康检查接口:
bash
curl http://localhost:8848/nacos/v1/console/health/readiness
# 返回 "OK" 即启动成功
6.6 访问控制台
浏览器打开:http://localhost:8848/nacos
- 默认用户名:
nacos - 默认密码:
nacos
登录后你会看到左侧菜单两大模块:
Nacos 控制台
├── 配置管理 ← 配置中心相关
│ ├── 配置列表
│ ├── 历史版本
│ └── 监听查询
├── 服务管理 ← 注册中心相关
│ ├── 服务列表 ← 看哪些服务注册了
│ ├── 订阅者列表
│ └── 实例列表
├── 权限控制 ← 用户、角色、权限
├── 命名空间 ← 环境隔离(dev/test/prod)
└── 集群管理 ← 看 Nacos 自身的节点状态
第一次登录后必做的两件事:
- 修改默认密码(即使是本地学习也建议改):权限控制 → 用户列表 → 编辑 nacos 用户
- 创建命名空间 :命名空间 → 新建命名空间 → 输入
dev、test、prod(后续配置和服务注册要用)
6.7 Docker 部署(推荐)
实际工作中我们都用 Docker,省去环境准备的麻烦:
最简版(学习用):
bash
docker run -d \
--name nacos \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
-e MODE=standalone \
nacos/nacos-server:v2.3.0
生产推荐版(带数据持久化):
bash
docker run -d \
--name nacos \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
-e MODE=standalone \
-e JVM_XMS=512m \
-e JVM_XMX=512m \
-v /opt/nacos/logs:/home/nacos/logs \
-v /opt/nacos/data:/home/nacos/data \
-v /opt/nacos/conf:/home/nacos/conf \
--restart=always \
nacos/nacos-server:v2.3.0
几个参数解释:
-v /opt/nacos/logs:/home/nacos/logs:日志持久化(容器删了日志还在)-v /opt/nacos/data:/home/nacos/data:数据持久化(关键!否则容器删了配置全没)JVM_XMS / JVM_XMX:JVM 堆内存大小,默认 1G,学习环境可以调小--restart=always:服务器重启后自动拉起 Nacos
6.8 常见启动问题排查
问题 1:启动后立刻闪退(Windows)
原因:默认集群模式找不到节点。
解决:用 startup.cmd -m standalone,不要直接双击。
问题 2:端口被占用
bash
# Windows 查看占用 8848 的进程
netstat -ano | findstr 8848
# 然后用 taskkill /PID xxx /F 杀掉
# Linux/Mac
lsof -i:8848
或者改 Nacos 默认端口:编辑 conf/application.properties,找到 server.port=8848 改成其他端口。
问题 3:内存不够
报错:Cannot allocate memory。改 bin/startup.sh(或 startup.cmd)里的 JVM 参数,把 -Xms2g -Xmx2g 改小到 -Xms512m -Xmx512m。
问题 4:客户端连不上(启动了但客户端注册失败)
90% 是因为 9848 端口没开。Nacos 2.x 用 gRPC 通信,必须放行 9848。
7. 实战一:服务注册与发现
这一节我们做一个完整的"调用链"演示:
order-service通过 Nacos 找到user-service并调用它。建议你跟着代码敲一遍,敲完就懂了。
7.1 项目结构总览
我们要建两个 Spring Boot 项目(或者一个父工程下的两个模块):
nacos-demo/
├── user-service/ ← 服务提供者(被调用方)
│ ├── pom.xml
│ └── src/main/
│ ├── java/com/example/user/
│ │ ├── UserServiceApplication.java
│ │ └── UserController.java
│ └── resources/
│ └── application.yml
│
└── order-service/ ← 服务消费者(调用方)
├── pom.xml
└── src/main/
├── java/com/example/order/
│ ├── OrderServiceApplication.java
│ ├── OrderController.java
│ └── UserClient.java ← Feign 客户端
└── resources/
└── application.yml
7.2 版本选型(重要!)
新手最容易在这里翻车------Spring Boot、Spring Cloud、Spring Cloud Alibaba 三者的版本必须严格对应,不然各种诡异报错。
推荐组合(截至 2024 年):
| Spring Boot | Spring Cloud | Spring Cloud Alibaba |
|---|---|---|
| 3.2.x | 2023.0.x | 2023.0.1.0 |
| 3.0.x ~ 3.1.x | 2022.0.x | 2022.0.0.0 |
| 2.7.x | 2021.0.x | 2021.0.5.0(推荐稳定版) |
| 2.6.x | 2021.0.x | 2021.0.4.0 |
完整对应表见:Spring Cloud Alibaba 版本说明
7.3 服务提供者(user-service)
第一步:父 POM 锁定版本(推荐做法)
用 <dependencyManagement> 统一管理版本,子模块就不用每个都写版本号:
xml
<!-- 父 pom.xml -->
<properties>
<spring-boot.version>2.7.18</spring-boot.version>
<spring-cloud.version>2021.0.8</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<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>
第二步:子模块引入依赖
xml
<!-- user-service/pom.xml -->
<dependencies>
<!-- Web 框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Nacos 服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
小贴士 :注意是
nacos-discovery(注册中心),不是nacos-config(配置中心)。两个 Starter 是分开的,可以单独使用,也可以一起用。
第三步:配置文件 application.yml
yaml
spring:
application:
name: user-service # 服务名(注册到 Nacos 用这个名字)
cloud:
nacos:
discovery:
server-addr: localhost:8848 # Nacos 服务端地址
namespace: dev # 命名空间 ID(注意:填的是 ID 不是名字,默认 public 留空)
group: DEFAULT_GROUP # 分组
username: nacos # Nacos 控制台账号(开了鉴权才需要)
password: nacos
# 可选高级配置
cluster-name: BJ # 集群名(同地域的实例会优先调用同集群的)
weight: 1.0 # 权重(多实例时影响负载均衡)
ephemeral: true # 临时实例(默认 true,用心跳维持)
server:
port: 8081
所有重要配置项详解:
| 配置项 | 含义 | 何时需要 |
|---|---|---|
spring.application.name |
服务名 | 必填 |
server-addr |
Nacos 地址(多个用逗号分隔) | 必填 |
namespace |
命名空间 ID | 多环境隔离时 |
group |
分组 | 多业务线隔离时 |
username / password |
鉴权账号 | Nacos 开启了鉴权 |
cluster-name |
集群名 | 跨地域部署,希望就近调用 |
weight |
权重 0-1 | 多实例做权重负载均衡 |
ephemeral |
临时实例 | 默认 true 即可 |
新手必坑 :
namespace填的是命名空间 ID (一串 UUID),不是命名空间名字!public命名空间的 ID 是空字符串,留空即可。在 Nacos 控制台 → 命名空间页面可以看到 ID。
第四步:启动类
java
@SpringBootApplication
@EnableDiscoveryClient // ⭐ 开启服务注册与发现
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
老手补充 :在新版 Spring Cloud(2020+)里,
@EnableDiscoveryClient其实是可省略的------只要引入了 nacos-discovery 依赖,自动配置会自己开启注册功能。但为了代码可读性,建议还是写上。
第五步:写一个接口
java
@RestController
public class UserController {
@Value("${server.port}")
private Integer port;
@GetMapping("/users/{id}")
public Map<String, Object> getUser(@PathVariable Long id) {
Map<String, Object> user = new HashMap<>();
user.put("id", id);
user.put("name", "张三");
user.put("port", port); // 返回端口,方便观察是哪个实例响应的
return user;
}
}
第六步:验证服务注册
启动应用,三种方式验证注册成功:
方式一:看控制台
Nacos 控制台 → 服务管理 → 服务列表 → 看到 user-service,实例数为 1。
方式二:调 Nacos REST API
bash
curl "http://localhost:8848/nacos/v1/ns/instance/list?serviceName=user-service&namespaceId=dev"
返回的 JSON 中 hosts 数组包含你的实例信息。
方式三:看启动日志
应用启动日志中应有这一行:
nacos registry, DEFAULT_GROUP user-service 192.168.x.x:8081 register finished
7.4 服务消费者(order-service)
第一步:依赖和配置(和 Provider 一样)
pom.xml 加 nacos-discovery 依赖,application.yml 配 Nacos 地址。唯一区别是 spring.application.name=order-service,端口改成 8082。
第二步:启动类------注入负载均衡的 RestTemplate
java
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServiceApplication {
@Bean
@LoadBalanced // 关键注解:让 RestTemplate 支持服务名调用 + 负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
老手原理小贴士 :
@LoadBalanced的本质是给 RestTemplate 添加了一个LoadBalancerInterceptor拦截器。每次请求时,拦截器从DiscoveryClient拿到服务实例列表,按负载均衡策略(默认轮询)选一个,把 URL 中的服务名替换成实际 IP+端口。所以"服务名 → IP"的解析是发生在请求拦截环节,而不是 DNS 层。
第三步:通过服务名调用
java
@RestController
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/orders/{userId}")
public Map<String, Object> getOrder(@PathVariable Long userId) {
// 注意:这里用的是服务名 "user-service",不是 IP 地址!
Map user = restTemplate.getForObject(
"http://user-service/users/" + userId,
Map.class
);
Map<String, Object> order = new HashMap<>();
order.put("orderId", 1001);
order.put("user", user);
return order;
}
}
启动后访问 http://localhost:8082/orders/1,会看到 order-service 成功通过 Nacos 调用到了 user-service。
7.5 验证负载均衡(多实例测试)
光跑一个实例看不出负载均衡,我们启动两个 user-service 实例:
步骤:
-
启动第一个实例:端口 8081(默认配置)
-
复制启动配置,把端口改成 8083 启动第二个:
- IDEA:右键启动配置 → Copy Configuration → 在 VM options 加
-Dserver.port=8083 - 命令行:
java -jar user-service.jar --server.port=8083
- IDEA:右键启动配置 → Copy Configuration → 在 VM options 加
-
在 Nacos 控制台 → 服务列表 → 点
user-service详情,应该看到 2 个实例 -
多次访问
http://localhost:8082/orders/1,观察返回的port字段:
json
// 第一次:{"id":1, "name":"张三", "port":8081}
// 第二次:{"id":1, "name":"张三", "port":8083}
// 第三次:{"id":1, "name":"张三", "port":8081}
// ...轮询切换
这就是负载均衡在工作。 默认策略是轮询(Round Robin)。
7.6 用 OpenFeign 调用(推荐方式)
实际项目中我们很少直接用 RestTemplate(拼 URL 容易出错、不好测试),更常用的是 OpenFeign------把 HTTP 调用伪装成 Java 接口方法。
第一步:引入依赖
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- Spring Cloud 2022+ 还需要这个负载均衡器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
第二步:启动类开启 Feign
java
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients // ⭐ 开启 Feign
public class OrderServiceApplication { /* ... */ }
第三步:定义 Feign 客户端接口
java
@FeignClient(name = "user-service") // 服务名
public interface UserClient {
@GetMapping("/users/{id}")
Map<String, Object> getUser(@PathVariable("id") Long id);
}
新手必坑 :
@PathVariable("id")的 value 必须显式写!否则 Feign 解析不到参数名。这是 Feign 的限制。
第四步:使用------像调本地方法一样
java
@RestController
public class OrderController {
@Autowired
private UserClient userClient; // 直接注入,像注入 Service 一样
@GetMapping("/orders/{userId}")
public Map<String, Object> getOrder(@PathVariable Long userId) {
Map user = userClient.getUser(userId); // 像调本地方法
// ... 业务逻辑
return order;
}
}
对比 RestTemplate 的优势:
- 类型安全(不用强转 Map)
- 不用拼 URL
- 接口契约清晰,方便单元测试
- 容易加超时、降级、重试等增强功能
7.7 Feign 进阶配置
配置超时时间
yaml
# application.yml
feign:
client:
config:
default: # 全局配置
connect-timeout: 3000 # 连接超时(毫秒)
read-timeout: 5000 # 读超时
user-service: # 针对特定服务的配置
connect-timeout: 1000
read-timeout: 2000
开启日志
yaml
# application.yml
logging:
level:
com.example.order.UserClient: DEBUG # 你的 Feign 接口类
feign:
client:
config:
default:
logger-level: FULL # NONE / BASIC / HEADERS / FULL
服务降级(结合 Sentinel)
java
@FeignClient(name = "user-service", fallback = UserClientFallback.class)
public interface UserClient {
@GetMapping("/users/{id}")
Map<String, Object> getUser(@PathVariable("id") Long id);
}
@Component
public class UserClientFallback implements UserClient {
@Override
public Map<String, Object> getUser(Long id) {
// 服务挂了或调不通时,返回兜底数据
Map<String, Object> fallback = new HashMap<>();
fallback.put("id", id);
fallback.put("name", "默认用户");
return fallback;
}
}
7.8 服务调用排查清单
调用失败时按这个顺序排查:
- 服务有没有注册成功? → Nacos 控制台看实例列表
- 服务名拼对了吗? → 检查
@FeignClient(name="...")和 Provider 的application.name - 命名空间和分组对得上吗? → Provider 注册到 dev,Consumer 也得在 dev 找
- 端口能通吗? → 在 Consumer 机器上
telnet provider-ip 8081 - 9848 端口开了吗? → Nacos 2.x 客户端必须能访问 9848
- 看 Provider 日志:有没有报错?
OpenFeign + Nacos 是微服务调用的黄金组合。 它把 HTTP 调用伪装成接口方法,代码可读性飙升。
8. 实战二:配置中心
配置中心的核心价值是:改配置不重启服务。这一节我们从控制台创建配置开始,一步步打通 Spring Boot 应用读取 Nacos 配置的全流程,并验证动态刷新效果。
8.1 在 Nacos 控制台创建配置
登录 Nacos 控制台 → 配置管理 → 配置列表 → 选好命名空间(比如 dev)→ 点击右上角 "+" 号。
填写以下内容:
| 字段 | 填什么 | 说明 |
|---|---|---|
| Data ID | user-service.yml |
配置文件的唯一标识(必须以扩展名结尾) |
| Group | DEFAULT_GROUP |
分组,默认即可 |
| 配置格式 | YAML |
选择 YAML(也可以是 properties、JSON 等) |
| 配置内容 | 见下方 | 配置的实际内容 |
配置内容示例:
yaml
user:
welcome-message: "欢迎来到用户服务"
max-login-attempts: 5
features:
enable-cache: true
cache-ttl: 300
点击 "发布"。发布后在配置列表能看到这条记录。
重点理解 :你刚才创建的这份配置,就是 Nacos 上的一份配置文件 。后面 Spring Boot 应用启动时会去 Nacos 拉它,效果跟在本地放一份
application.yml一样。
8.2 DataId 命名规则------很重要!
新手最容易在这里踩坑:Spring Boot 应用启动时按什么规则去 Nacos 找配置?
完整的 DataId 拼接公式是:
${spring.application.name}-${spring.profiles.active}.${file-extension}
举例:
| 应用配置 | 拼接出的 DataId |
|---|---|
name=user-service,active 不设置,file-extension=yml |
user-service.yml |
name=user-service,active=dev,file-extension=yml |
user-service-dev.yml |
name=user-service,active=prod,file-extension=properties |
user-service-prod.properties |
所以你在 Nacos 上创建的 DataId,必须和应用拼出来的名字一模一样,否则就拉不到配置。
8.3 Spring Boot 项目读取配置
第一步:引入依赖
xml
<!-- pom.xml -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- ⭐ Spring Cloud 2020.0+ 必须额外加这个依赖,否则 bootstrap.yml 不生效 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
新手必坑 :从 Spring Cloud 2020.0 开始,
bootstrap上下文默认被关闭了。要让bootstrap.yml生效,必须 额外引入spring-cloud-starter-bootstrap依赖。这是大量初学者卡住的地方。
第二步:bootstrap.yml vs application.yml------为什么要分两个?
| 文件 | 加载时机 | 作用 |
|---|---|---|
bootstrap.yml |
应用启动最早期(在 application.yml 之前) | 配置 Nacos 地址等"如何拉远程配置"的信息 |
application.yml |
拉完远程配置之后 | 应用本身的业务配置 |
为什么必须分开? 想想这个鸡生蛋问题:
应用启动
↓
要从 Nacos 拉配置(但 Nacos 地址在哪?)
↓
答:在 bootstrap.yml 里 ← 必须先读这个
↓
拉到远程配置后,和 application.yml 合并
↓
应用真正启动完成
经验法则:
- 跟 Nacos 通信相关的配置(
server-addr、namespace、group、file-extension)→ 放bootstrap.yml - 业务配置(端口、数据库连接、自定义参数)→ 放远程 Nacos 或本地
application.yml
第三步:创建 bootstrap.yml
yaml
# src/main/resources/bootstrap.yml
spring:
application:
name: user-service # ⭐ 这个名字 + 后缀就是 DataId
profiles:
active: dev # ⭐ 影响 DataId 拼接 → user-service-dev.yml
cloud:
nacos:
config:
server-addr: localhost:8848
namespace: dev # 命名空间 ID(注意填 ID 不是名字!)
group: DEFAULT_GROUP
file-extension: yml # ⭐ 必填!告诉 Nacos 配置格式
username: nacos
password: nacos
# 可选高级配置
timeout: 3000 # 拉取配置超时时间(毫秒)
refresh-enabled: true # 是否开启动态刷新(默认 true)
重要配置项详解:
| 配置项 | 含义 | 备注 |
|---|---|---|
server-addr |
Nacos 地址 | 必填 |
namespace |
命名空间 ID | 不填默认 public |
group |
分组 | 不填默认 DEFAULT_GROUP |
file-extension |
配置格式 | 必填!默认是 properties,不写会拉错 |
prefix |
DataId 前缀 | 默认是 application.name,可单独覆盖 |
refresh-enabled |
动态刷新开关 | 默认 true |
第四步:写一个 Controller 读取配置
java
@RestController
@RefreshScope // ⭐ 关键注解:支持配置动态刷新
public class ConfigController {
@Value("${user.welcome-message}")
private String welcomeMessage;
@Value("${user.max-login-attempts}")
private Integer maxLoginAttempts;
@GetMapping("/config")
public Map<String, Object> getConfig() {
Map<String, Object> result = new HashMap<>();
result.put("welcomeMessage", welcomeMessage);
result.put("maxLoginAttempts", maxLoginAttempts);
return result;
}
}
老手原理小贴士 :
@RefreshScope注解的 Bean 实际上是代理对象 ,每次调用方法时检查配置是否变化,变化了就重建 Bean 实例。所以严格来说不是"修改字段值",而是"换了一个新 Bean"。这也是为什么没加@RefreshScope的 Bean 拿到的就是启动时的旧值------它根本没机会被重建。
8.4 启动并验证(关键步骤!)
验证一:配置成功拉取
启动应用,看启动日志,应该有这一行:
[Nacos Config] Load config[dataId=user-service-dev.yml, group=DEFAULT_GROUP] ...
看到这行说明配置拉到了。如果没看到或报错,跳到 8.7 排查清单。
验证二:配置生效
访问 http://localhost:8081/config,应该返回:
json
{
"welcomeMessage": "欢迎来到用户服务",
"maxLoginAttempts": 5
}
验证三:动态刷新(这是高潮!)
- 不要关闭应用
- 去 Nacos 控制台编辑
user-service-dev.yml,把welcome-message改成"Hi, 新用户!",点击发布 - 立刻再访问
http://localhost:8081/config,应该看到:
json
{
"welcomeMessage": "Hi, 新用户!", ← 已经变了!
"maxLoginAttempts": 5
}
整个过程没有重启服务,配置秒级生效。 这就是配置中心最爽的体验。
背后发生了什么 :Nacos 客户端通过长轮询 机制监听配置变化(详见第 11 章面试题 Q3)。一旦 Nacos 上配置变更,客户端立刻收到通知,触发 Spring 上下文刷新事件,所有
@RefreshScope的 Bean 在下次访问时重建。
8.5 更优雅的做法:使用配置类
@Value 一个一个字段写太繁琐,复杂配置推荐用 @ConfigurationProperties:
java
@Component
@ConfigurationProperties(prefix = "user")
@RefreshScope
@Data // Lombok:自动生成 getter/setter
public class UserConfig {
private String welcomeMessage;
private Integer maxLoginAttempts;
private Features features;
@Data
public static class Features {
private Boolean enableCache;
private Integer cacheTtl;
}
}
使用:
java
@RestController
public class ConfigController {
@Autowired
private UserConfig userConfig; // 直接注入整个配置对象
@GetMapping("/config2")
public UserConfig getConfig() {
return userConfig;
// 嵌套对象、列表、Map 都自动绑定,比 @Value 强大得多
}
}
@ConfigurationProperties 比 @Value 的优势:
- ✅ 支持嵌套对象、列表、Map
- ✅ 类型安全(编译期检查)
- ✅ IDE 自动补全
- ✅ 一个类管理一组配置,结构清晰
- ✅ 可以加 JSR-303 校验注解(
@NotNull、@Min等)
8.6 多配置文件加载(实战常用)
实际项目中你可能要加载多份配置(公共配置 + 业务配置):
yaml
# bootstrap.yml
spring:
application:
name: user-service
cloud:
nacos:
config:
server-addr: localhost:8848
namespace: dev
file-extension: yml
# ⭐ 主配置:自动拉 user-service.yml 和 user-service-dev.yml
# ⭐ 共享配置(所有微服务共用)
shared-configs:
- data-id: common-db.yml # 公共数据库配置
group: DEFAULT_GROUP
refresh: true
- data-id: common-redis.yml # 公共 Redis 配置
group: DEFAULT_GROUP
refresh: true
# ⭐ 扩展配置(自定义额外加载的配置)
extension-configs:
- data-id: user-rules.yml # 业务规则配置
group: USER_GROUP
refresh: true
加载优先级(从低到高):
shared-configs ← 优先级最低
↓
extension-configs
↓
${application.name}.${ext} ← 主配置
↓
${application.name}-${profile}.${ext} ← 优先级最高
↓
本地 application.yml ← 实际生效
后加载的会覆盖先加载的同名配置。
8.7 配置拉取失败排查清单
按以下顺序检查:
-
bootstrap.yml真的生效了吗?- Spring Cloud 2020+ 必须引入
spring-cloud-starter-bootstrap依赖 - 启动日志看不到
Bootstrap上下文相关日志说明它没起作用
- Spring Cloud 2020+ 必须引入
-
DataId 拼对了吗?
- 公式:
${name}-${profile}.${ext}或${name}.${ext}(无 profile 时) - 启动日志会打印实际去拉的 DataId,对照 Nacos 控制台
- 公式:
-
file-extension写了吗?- 默认是
properties,但你创建的是 yml → 找不到 - 必须显式指定
file-extension: yml
- 默认是
-
命名空间填错了吗?
- 填的是命名空间 ID(UUID),不是名字
- public 命名空间留空,不要写
public
-
分组对吗?
- 默认
DEFAULT_GROUP,如果配置在别的组就拉不到
- 默认
-
网络通吗?
telnet localhost 8848测试连通性
-
鉴权账号对吗?
- Nacos 开启了鉴权但没配
username/password→ 401
- Nacos 开启了鉴权但没配
8.8 动态刷新不生效排查清单
配置改了,但应用拿到的还是旧值?检查:
- 类上加了
@RefreshScope吗? → 没加就不会刷新 - 是 static 字段吗? →
@Value不支持 static refresh-enabled设成 false 了吗? → 全局关闭就没刷新了- 配置项的 key 拼对了吗? → 改了 Nacos 但 key 不一样,自然不生效
- Nacos 配置的"格式"对吗? → 创建配置时选错了格式(比如选了 properties 但写的是 yml 内容)
8.9 通用配置代码片段(直接复制用)
下面是一个可以直接用在生产项目的标准配置:
yaml
# bootstrap.yml
spring:
application:
name: ${APP_NAME:user-service}
profiles:
active: ${SPRING_PROFILES_ACTIVE:dev}
cloud:
nacos:
config:
server-addr: ${NACOS_SERVER:localhost:8848}
namespace: ${NACOS_NAMESPACE:}
group: ${NACOS_GROUP:DEFAULT_GROUP}
file-extension: yml
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:nacos}
shared-configs:
- data-id: common.yml
group: DEFAULT_GROUP
refresh: true
discovery:
server-addr: ${NACOS_SERVER:localhost:8848}
namespace: ${NACOS_NAMESPACE:}
group: ${NACOS_GROUP:DEFAULT_GROUP}
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:nacos}
为什么用 ${XXX:default} 形式? 这样在 Docker 部署时可以通过环境变量覆盖,不用改代码-------e NACOS_SERVER=192.168.1.100:8848 就能切换 Nacos 地址。生产实战必备技巧。
9. 进阶用法
9.1 共享配置------多个服务共用一份配置
很多服务有公共配置(比如数据库、Redis 连接),可以抽成共享配置:
yaml
# bootstrap.yml
spring:
cloud:
nacos:
config:
server-addr: localhost:8848
# 扩展配置:从多个 DataId 加载配置
extension-configs:
- data-id: common-db.yml # 共享的数据库配置
group: DEFAULT_GROUP
refresh: true # 支持动态刷新
- data-id: common-redis.yml # 共享的 Redis 配置
group: DEFAULT_GROUP
refresh: true
这样多个服务可以共享一份配置,修改一处所有服务都更新。
9.2 按环境加载不同配置(Profile)
Nacos 支持根据 spring.profiles.active 自动加载不同的配置文件:
Nacos 上创建三份配置:
├── user-service-dev.yml # 开发环境
├── user-service-test.yml # 测试环境
└── user-service-prod.yml # 生产环境
yaml
# bootstrap.yml
spring:
application:
name: user-service
profiles:
active: dev # 自动加载 user-service-dev.yml
9.3 配置加密
敏感配置(密码、密钥)不能明文放在 Nacos 上。Nacos 支持通过集成 Jasypt、KMS 等工具做配置加密。
9.4 灰度发布
Nacos 2.x 支持灰度发布:先把新配置推给一部分实例,验证没问题再全量推送。降低配置变更的风险。
9.5 监听配置变化
如果你想在配置变化时执行特定逻辑(比如重新初始化某个资源),可以主动监听:
java
@Component
public class ConfigChangeListener {
@NacosConfigListener(dataId = "user-service.yml")
public void onChange(String newConfig) {
System.out.println("配置变更:" + newConfig);
// 执行你的逻辑
}
}
10. 生产环境部署
10.1 生产环境不能用单机模式
-m standalone 是单机模式,挂了就全挂。生产环境必须用集群模式(至少 3 个节点)。
10.2 集群部署架构
┌──────────────┐
│ 负载均衡(Nginx)│
└──────────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│Nacos 节点1│ │Nacos 节点2│ │Nacos 节点3│
└─────────┘ └─────────┘ └─────────┘
│ │ │
└─────────────┼─────────────┘
▼
┌──────────────┐
│ MySQL 集群 │
│ (存储配置) │
└──────────────┘
10.3 关键配置
一、使用 MySQL 存储配置
默认用内嵌的 Derby 数据库,生产环境必须改用 MySQL:
properties
# application.properties
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://localhost:3306/nacos_config
db.user=root
db.password=xxx
二、修改默认密码
默认 nacos/nacos 是公开的,生产环境必须改。
三、开启权限控制
properties
nacos.core.auth.enabled=true
nacos.core.auth.server.identity.key=your-key
nacos.core.auth.server.identity.value=your-value
10.4 高可用建议
- 至少 3 个 Nacos 节点(避免脑裂)
- MySQL 主从部署
- 前面加 Nginx 做负载均衡
- 磁盘用 SSD(配置读写对磁盘 IO 敏感)
- 监控:接入 Prometheus + Grafana 监控 Nacos 状态
11. 常见面试题
Q1:Nacos 和 Eureka 的区别?
主要区别有三点:
- 功能范围:Nacos 是注册中心 + 配置中心二合一,Eureka 只是注册中心
- 一致性模型:Nacos 支持 AP/CP 切换,Eureka 只支持 AP
- 维护状态:Nacos 活跃迭代中,Eureka 2.x 已停止维护
新项目建议直接选 Nacos。
Q2:Nacos 的 AP 和 CP 模式有什么区别?
- AP 模式(默认):优先保证可用性。用于临时实例(Ephemeral),服务挂了 Nacos 会快速剔除,适合普通微服务。
- CP 模式:优先保证一致性。用于永久实例(Persistent),数据通过 Raft 协议强一致,适合数据库主备这种对数据准确性要求高的场景。
Q3:Nacos 如何做到配置动态刷新?
客户端通过长轮询机制监听配置变化:
- 客户端向 Nacos 发起 HTTP 请求,Nacos 挂起这个请求最多 30 秒
- 如果 30 秒内配置有变化,Nacos 立刻返回新配置
- 如果没变化,30 秒后返回空,客户端继续发下一次长轮询
配合 Spring Cloud 的
@RefreshScope注解,收到新配置后会刷新相关 Bean 的属性。
Q4:命名空间、分组、DataId 的区别?
这是 Nacos 的三级隔离机制:
- 命名空间(Namespace):最高级别隔离,通常用于区分环境(dev/test/prod)
- 分组(Group):命名空间内的分组,通常按业务线划分
- DataId:配置的具体名字(通常是配置文件名)
三者结合唯一确定一份配置。
Q5:服务注册到 Nacos 后,Nacos 怎么知道服务还活着?
通过心跳机制:
- 临时实例(默认):客户端每 5 秒向 Nacos 发一次心跳,Nacos 15 秒没收到心跳就标记为不健康,30 秒没收到就剔除
- 永久实例:由 Nacos 主动探测(TCP/HTTP 健康检查)
Q6:Nacos 生产环境要注意什么?
五个关键点:
- 必须用集群模式(至少 3 节点)
- 必须改用 MySQL 存储(默认的 Derby 不能扩展)
- 必须修改默认密码
- 开启权限控制
- 前面加 Nginx 负载均衡 + 监控告警
12. 总结
12.1 核心要点回顾
- Nacos 是什么:阿里开源的"注册中心 + 配置中心"二合一中间件
- 它解决什么:微服务的两大痛点------服务互相找不到、配置散落各处
- 两大核心功能:服务注册与发现、分布式配置管理
- 六个核心概念:服务、实例、命名空间、分组、DataId、集群
- 典型用法:Spring Cloud Alibaba + OpenFeign + Nacos,微服务的黄金组合
12.2 学习路径建议
- 搭建 Nacos 服务端(Docker 一条命令)
- 跑通服务注册示例(Provider + Consumer)
- 跑通配置中心示例(体验动态刷新的惊艳)
- 学习进阶功能(共享配置、多环境、灰度发布)
- 了解生产部署(集群、MySQL、权限、监控)
12.3 一句话记住 Nacos
微服务架构里两件麻烦事------"服务怎么找"和"配置怎么管"------Nacos 一个中间件全搞定。这就是它在国内微服务生态里称王的原因。
参考资料: