Nacos 详解——从概念到实战

在微服务架构中,你会频繁听到 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]  │    │
│  └─────────────────────────────────────────┘    │
└─────────────────────────────────────────────────┘
        ▲                           │
        │ ① 注册                    │ ② 查询
        │                           ▼
   ┌─────────┐              ┌──────────────┐
   │ 用户服务 │              │  订单服务     │
   │ 启动时   │              │  调用时       │
   │ 上报地址  │              │  拉取列表     │
   └─────────┘              └──────────────┘

关键动作:

  1. 服务注册:服务启动时,告诉 Nacos "我在这"
  2. 服务发现:调用方从 Nacos 拉取服务的地址列表
  3. 心跳检测:服务定期向 Nacos 发心跳,Nacos 剔除挂掉的服务
  4. 负载均衡:调用方拿到多个地址后,按策略(轮询、随机等)选一个调用

3.2 功能二:配置中心(Configuration)

复制代码
配置中心工作流程:
═══════════════════════════════════════

  运维/开发人员
       │
       │ ① 在控制台修改配置
       ▼
┌─────────────────────────┐
│    Nacos 服务端          │
│  ┌─────────────────┐   │
│  │ 配置仓库          │   │
│  │ ├── user-svc.yml │   │
│  │ ├── order-svc.yml│   │
│  │ └── common.yml   │   │
│  └─────────────────┘   │
└─────────────────────────┘
       │
       │ ② 推送变更(长轮询)
       ▼
┌──────────────────────────────────────┐
│  应用 A       应用 B      应用 C       │
│  收到变更     收到变更     收到变更      │
│  自动刷新 Bean 属性                    │
└──────────────────────────────────────┘
       │
       │ ③ 无需重启,配置实时生效
       ▼
    业务继续跑

关键特性:

  1. 集中管理:所有配置放在 Nacos 上
  2. 动态推送:配置变更自动推送给订阅的服务
  3. 版本管理:每次修改都有历史记录,可以一键回滚
  4. 多环境隔离: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_GROUPUSER_GROUPPAY_GROUP
  • 按部门分组:TEAM_ATEAM_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.loglogs/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 自身的节点状态

第一次登录后必做的两件事:

  1. 修改默认密码(即使是本地学习也建议改):权限控制 → 用户列表 → 编辑 nacos 用户
  2. 创建命名空间 :命名空间 → 新建命名空间 → 输入 devtestprod(后续配置和服务注册要用)

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.xmlnacos-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 实例

步骤:

  1. 启动第一个实例:端口 8081(默认配置)

  2. 复制启动配置,把端口改成 8083 启动第二个:

    • IDEA:右键启动配置 → Copy Configuration → 在 VM options 加 -Dserver.port=8083
    • 命令行:java -jar user-service.jar --server.port=8083
  3. 在 Nacos 控制台 → 服务列表 → 点 user-service 详情,应该看到 2 个实例

  4. 多次访问 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 服务调用排查清单

调用失败时按这个顺序排查:

  1. 服务有没有注册成功? → Nacos 控制台看实例列表
  2. 服务名拼对了吗? → 检查 @FeignClient(name="...") 和 Provider 的 application.name
  3. 命名空间和分组对得上吗? → Provider 注册到 dev,Consumer 也得在 dev 找
  4. 端口能通吗? → 在 Consumer 机器上 telnet provider-ip 8081
  5. 9848 端口开了吗? → Nacos 2.x 客户端必须能访问 9848
  6. 看 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-serviceactive 不设置,file-extension=yml user-service.yml
name=user-serviceactive=devfile-extension=yml user-service-dev.yml
name=user-serviceactive=prodfile-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-addrnamespacegroupfile-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
}
验证三:动态刷新(这是高潮!)
  1. 不要关闭应用
  2. 去 Nacos 控制台编辑 user-service-dev.yml,把 welcome-message 改成 "Hi, 新用户!",点击发布
  3. 立刻再访问 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 配置拉取失败排查清单

按以下顺序检查:

  1. bootstrap.yml 真的生效了吗?

    • Spring Cloud 2020+ 必须引入 spring-cloud-starter-bootstrap 依赖
    • 启动日志看不到 Bootstrap 上下文相关日志说明它没起作用
  2. DataId 拼对了吗?

    • 公式:${name}-${profile}.${ext}${name}.${ext}(无 profile 时)
    • 启动日志会打印实际去拉的 DataId,对照 Nacos 控制台
  3. file-extension 写了吗?

    • 默认是 properties,但你创建的是 yml → 找不到
    • 必须显式指定 file-extension: yml
  4. 命名空间填错了吗?

    • 填的是命名空间 ID(UUID),不是名字
    • public 命名空间留空,不要写 public
  5. 分组对吗?

    • 默认 DEFAULT_GROUP,如果配置在别的组就拉不到
  6. 网络通吗?

    • telnet localhost 8848 测试连通性
  7. 鉴权账号对吗?

    • Nacos 开启了鉴权但没配 username/password → 401

8.8 动态刷新不生效排查清单

配置改了,但应用拿到的还是旧值?检查:

  1. 类上加了 @RefreshScope 吗? → 没加就不会刷新
  2. 是 static 字段吗?@Value 不支持 static
  3. refresh-enabled 设成 false 了吗? → 全局关闭就没刷新了
  4. 配置项的 key 拼对了吗? → 改了 Nacos 但 key 不一样,自然不生效
  5. 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 的区别?

主要区别有三点:

  1. 功能范围:Nacos 是注册中心 + 配置中心二合一,Eureka 只是注册中心
  2. 一致性模型:Nacos 支持 AP/CP 切换,Eureka 只支持 AP
  3. 维护状态:Nacos 活跃迭代中,Eureka 2.x 已停止维护

新项目建议直接选 Nacos。

Q2:Nacos 的 AP 和 CP 模式有什么区别?

  • AP 模式(默认):优先保证可用性。用于临时实例(Ephemeral),服务挂了 Nacos 会快速剔除,适合普通微服务。
  • CP 模式:优先保证一致性。用于永久实例(Persistent),数据通过 Raft 协议强一致,适合数据库主备这种对数据准确性要求高的场景。

Q3:Nacos 如何做到配置动态刷新?

客户端通过长轮询机制监听配置变化:

  1. 客户端向 Nacos 发起 HTTP 请求,Nacos 挂起这个请求最多 30 秒
  2. 如果 30 秒内配置有变化,Nacos 立刻返回新配置
  3. 如果没变化,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 生产环境要注意什么?

五个关键点:

  1. 必须用集群模式(至少 3 节点)
  2. 必须改用 MySQL 存储(默认的 Derby 不能扩展)
  3. 必须修改默认密码
  4. 开启权限控制
  5. 前面加 Nginx 负载均衡 + 监控告警

12. 总结

12.1 核心要点回顾

  • Nacos 是什么:阿里开源的"注册中心 + 配置中心"二合一中间件
  • 它解决什么:微服务的两大痛点------服务互相找不到、配置散落各处
  • 两大核心功能:服务注册与发现、分布式配置管理
  • 六个核心概念:服务、实例、命名空间、分组、DataId、集群
  • 典型用法:Spring Cloud Alibaba + OpenFeign + Nacos,微服务的黄金组合

12.2 学习路径建议

  1. 搭建 Nacos 服务端(Docker 一条命令)
  2. 跑通服务注册示例(Provider + Consumer)
  3. 跑通配置中心示例(体验动态刷新的惊艳)
  4. 学习进阶功能(共享配置、多环境、灰度发布)
  5. 了解生产部署(集群、MySQL、权限、监控)

12.3 一句话记住 Nacos

微服务架构里两件麻烦事------"服务怎么找"和"配置怎么管"------Nacos 一个中间件全搞定。这就是它在国内微服务生态里称王的原因。


参考资料

相关推荐
武子康8 分钟前
Java-07 深入浅出 MyBatis数据库一对多关系模型实战:表结构设计与查询实现
java·后端
REDcker2 小时前
Linux OverlayFS详解
java·linux·运维
Royzst2 小时前
xml知识点
java·服务器·前端
鱼鳞_3 小时前
苍穹外卖-Day08(缓存套餐)
java·redis·缓存
过期动态3 小时前
【LeetCode 热题 100】移动零
java·数据结构·算法·leetcode·职场和发展·rabbitmq
苏渡苇4 小时前
服务容错的必要性与Spring Cloud Alibaba Sentinel 限流配置实战
spring boot·spring cloud·sentinel
bug菌4 小时前
【SpringBoot 3.x 第254节】夯爆了,数据库访问性能优化实战详解!
数据库·spring boot·后端
sinat_255487814 小时前
IDEA:查找文件/类
java·ide·设计模式·intellij-idea
不肯过江东丶4 小时前
大聪明教你学Java | Spring AI Lab:一个让你 3 分钟接入 AI 对话能力的 Spring Boot 工具箱
spring boot·后端
AI人工智能+电脑小能手5 小时前
【大白话说Java面试题 第77题】【Mysql篇】第7题:回表查询与全表扫描的区别?
java·开发语言·数据库·mysql·面试