SpringCloudAlibaba_Nacos_Feign详解

O泡李华 11

Spring Cloud Alibaba:Nacos + Feign 详解

课程位置:第四阶段「微服务项目 + AI 应用」第 2 课
前置知识:微服务体系架构、Spring Boot、Maven、RESTful、HTTP、Spring MVC
本课主题:Spring Cloud Alibaba 组件——Nacos|Feign
下一课:Spring Cloud Alibaba——Sentinel
学习目标:从两个普通 Spring Boot 服务开始,亲自体验“写死 URL”的问题,再逐步完成 Nacos 服务注册、服务发现、Spring Cloud LoadBalancer、OpenFeign 声明式远程调用、多实例负载均衡、Namespace/Group/DataId、Nacos 配置中心、动态刷新、超时配置和常见错误排查。


一、这一课到底要解决什么问题

上一课我们已经知道:

Nacos
= 找服务 + 管配置

Feign
= 调服务

Sentinel
= 保护服务

Gateway
= 统一入口

这一课只做前两个:

Nacos
+
Feign

学习顺序非常重要:

先不用 Nacos
↓
先写死 URL 调用
↓
亲自看到地址写死的问题
↓
再引入 Nacos
↓
服务注册
↓
服务发现
↓
LoadBalancer
↓
再引入 Feign
↓
把 HTTP 调用封装成 Java 接口

这样你会真正理解:

为什么需要这些组件

而不是只会复制配置。


二、我们今天搭什么项目

创建父工程:

spring-cloud-demo

下面两个服务:

spring-cloud-demo
├─ user-service
└─ order-service

职责:

user-service
负责用户信息

order-service
负责订单信息

业务需求:

查询订单详情
↓
订单服务拿到 userId
↓
order-service 调用 user-service
↓
获取用户姓名
↓
组装订单详情

最终浏览器访问:

GET /orders/1001

返回类似:

{
  "orderId": 1001,
  "userId": 1,
  "userName": "用户-1",
  "productName": "机油套餐"
}

三、先确定版本,不要随便复制旧教程

微服务版本不能乱混。

本课使用 Spring Boot 3 的一条明确教学基线:

JDK 17

Spring Boot 3.5.x

Spring Cloud 2025.0.x

Spring Cloud Alibaba 2025.0.x

Nacos 3.x

Spring Cloud Alibaba 官方兼容表明确给出:

Spring Cloud Alibaba 2025.0.0.0
→ Spring Cloud 2025.0.0
→ Spring Boot 3.5.0
→ Nacos Client 3.0.3

所以课程示例可以使用:

Spring Boot 3.5.0
Spring Cloud 2025.0.0
Spring Cloud Alibaba 2025.0.0.0
JDK17

注意:

这是教学基线
不是说以后永远只能用这些 patch 版本

真正项目应该:

先确定 Boot
↓
查官方兼容矩阵
↓
使用 BOM
↓
让 BOM 管理组件版本

不要:

Nacos 一个版本
Feign 一个版本
Cloud 一个版本
全部手工乱填

四、BOM 是什么

BOM:

Bill of Materials

你可以理解为:

一整套经过搭配的依赖版本清单

微服务组件很多:

Spring Cloud Commons
OpenFeign
LoadBalancer
Nacos
Sentinel
Gateway

如果每个都自己写版本:

非常容易出现版本冲突

五、父工程 pom.xml

创建 Maven 父工程:

spring-cloud-demo
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="
           http://maven.apache.org/POM/4.0.0
           https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.0</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>spring-cloud-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <packaging>pom</packaging>

    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2025.0.0</spring-cloud.version>
        <spring-cloud-alibaba.version>2025.0.0.0</spring-cloud-alibaba.version>
    </properties>

    <modules>
        <module>user-service</module>
        <module>order-service</module>
    </modules>

    <dependencyManagement>
        <dependencies>

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

</project>

六、为什么 packaging 是 pom

父工程:

主要负责:
聚合模块
管理版本

它自己不作为:

Spring Boot 服务

所以:

<packaging>pom</packaging>

七、创建 user-service

IDEA:

右键父工程
→ New
→ Module
→ Maven

模块:

user-service

第一阶段只加 Web:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

八、user-service 配置

application.yml:

server:
  port: 8081

spring:
  application:
    name: user-service

这里:

spring.application.name

以后非常重要。

它相当于:

微服务逻辑名字

Nacos、Feign、Gateway 都会围绕它工作。


九、UserVO

课程先不接数据库:

public class UserVO {

    private Long id;

    private String name;

    public UserVO() {
    }

    public UserVO(
            Long id,
            String name
    ) {
        this.id = id;
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(
            String name
    ) {
        this.name = name;
    }
}

十、UserController

@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{id}")
    public UserVO getById(
            @PathVariable
            Long id
    ) {
        return new UserVO(
            id,
            "用户-" + id
        );
    }
}

启动:

http://localhost:8081/users/1

应该返回:

{
  "id": 1,
  "name": "用户-1"
}

十一、创建 order-service

模块:

order-service

第一阶段同样:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

配置:

server:
  port: 8082

spring:
  application:
    name: order-service

十二、先用最原始方式远程调用

为了真正理解 Nacos 的必要性,我们先写:

固定地址

order-service 创建:

@Configuration
public class RestConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

十三、OrderVO

public class OrderVO {

    private Long orderId;

    private Long userId;

    private String userName;

    private String productName;

    // getter / setter
}

十四、固定 localhost 调用

@RestController
@RequestMapping("/orders")
public class OrderController {

    private final RestTemplate
            restTemplate;

    public OrderController(
            RestTemplate restTemplate
    ) {
        this.restTemplate =
                restTemplate;
    }

    @GetMapping("/{id}")
    public OrderVO getById(
            @PathVariable Long id
    ) {

        Long userId = 1L;

        UserVO user =
                restTemplate.getForObject(
                    "http://localhost:8081/users/"
                    + userId,
                    UserVO.class
                );

        OrderVO vo =
                new OrderVO();

        vo.setOrderId(id);
        vo.setUserId(userId);
        vo.setUserName(
            user.getName()
        );
        vo.setProductName(
            "机油套餐"
        );

        return vo;
    }
}

十五、现在为什么能运行

启动:

user-service : 8081
order-service : 8082

访问:

http://localhost:8082/orders/1001

调用链:

Browser
↓
order-service
↓
RestTemplate
↓
localhost:8081
↓
user-service

十六、既然能运行,为什么还需要 Nacos

因为这里:

"http://localhost:8081"

被写死了。

如果 user-service:

8081 → 8085

order-service:

也必须改代码/配置

十七、真实生产更麻烦

user-service 可能扩成:

10.0.0.11:8081
10.0.0.12:8081
10.0.0.13:8081

并且实例会:

扩容
缩容
重启
故障

不能要求程序员:

手工维护所有服务 IP

十八、Nacos Discovery

Nacos 服务发现就是为了解决:

服务在哪里?

核心:

Provider 注册
↓
Nacos 保存实例
↓
Consumer 根据服务名发现实例

十九、安装 Nacos

当前 Nacos 已进入:

3.x

官方站点截至本笔记整理时显示稳定最新版:

3.2.4

但课程更重要的规则是:

按 Spring Cloud Alibaba
兼容矩阵选择 Nacos 3.x

而不是只追:

最新版

二十、Windows 启动 Nacos

解压后进入:

nacos/bin

执行:

startup.cmd -m standalone

standalone:

单机模式

适合:

本地学习

Linux / macOS:

sh startup.sh -m standalone

二十一、Nacos 安全提醒

官方 Quick Start 的 standalone:

只适合测试

生产应考虑:

集群
认证
网络隔离

不要:

无认证 Nacos 直接暴露公网

新版 Nacos 3.x 的认证初始化方式可能与老教程不同,因此:

不要死记老教程默认账号密码

以当前 Nacos 版本控制台提示为准。


二十二、Nacos 常见端口

本地常用:

8848

控制台通常访问:

http://127.0.0.1:8848/nacos

具体以当前版本启动日志为准。


二十三、user-service 接入 Discovery

pom 加:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>
        spring-cloud-starter-alibaba-nacos-discovery
    </artifactId>
</dependency>

注意:

不要写 version

因为:

父 BOM 已经管理

二十四、user-service application.yml

server:
  port: 8081

spring:
  application:
    name: user-service

  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

二十五、@EnableDiscoveryClient

课程中按官方快速入门写:

@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {

    public static void main(
            String[] args
    ) {
        SpringApplication.run(
            UserServiceApplication.class,
            args
        );
    }
}

现代 Spring Cloud 自动配置能力较强,部分组合中即使不显式写该注解也能自动注册。

但是课程阶段保留它更容易理解:

这个应用参与服务发现

二十六、启动后验证

Nacos 服务列表应该看到:

user-service

进入详情可以观察:

IP
Port
健康状态
权重
集群
Metadata

二十七、Nacos 注册的到底是什么

不是:

/users/{id}

这样的 Controller 路径。

Nacos 主要注册:

服务实例

例如:

serviceName:
user-service

ip:
127.0.0.1

port:
8081

Controller 路径仍由:

Spring MVC

处理。


二十八、order-service 接入 Discovery

同样添加:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>
        spring-cloud-starter-alibaba-nacos-discovery
    </artifactId>
</dependency>

配置:

server:
  port: 8082

spring:
  application:
    name: order-service

  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

启动后:

Nacos
├─ user-service
└─ order-service

二十九、现在是不是已经会自动远程调用

不是。

Nacos 解决的是:

找到服务实例

并不会自动替你:

调用 Controller

三十、DiscoveryClient

Spring Cloud 提供:

DiscoveryClient

可以手工查询实例。

private final DiscoveryClient
        discoveryClient;

public DemoController(
        DiscoveryClient discoveryClient
) {
    this.discoveryClient =
            discoveryClient;
}

三十一、查询实例

List<ServiceInstance> instances =
        discoveryClient.getInstances(
            "user-service"
        );

打印:

for (
    ServiceInstance instance
    :
    instances
) {
    System.out.println(
        instance.getHost()
        +
        ":"
        +
        instance.getPort()
    );
}

三十二、手工选择实例的问题

可以:

ServiceInstance instance =
        instances.get(0);

然后拼:

String url =
        "http://"
        + instance.getHost()
        + ":"
        + instance.getPort()
        + "/users/"
        + userId;

但是这意味着:

你自己负责选实例

如果有:

5 个实例

到底选哪个?

于是引出:

LoadBalancer

三十三、Spring Cloud LoadBalancer

加入:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>
        spring-cloud-starter-loadbalancer
    </artifactId>
</dependency>

有时其他 Starter 会间接带入,但课程建议:

显式加入

更容易知道它的职责。


三十四、@LoadBalanced

@Configuration
public class RestConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

三十五、URL 从 IP 变服务名

原来:

http://localhost:8081/users/1

现在:

http://user-service/users/1

代码:

UserVO user =
        restTemplate.getForObject(
            "http://user-service/users/"
            + userId,
            UserVO.class
        );

三十六、user-service 是域名吗

不是普通 DNS 域名。

这里:

user-service

是:

服务名

三十七、背后调用链

RestTemplate
↓
@LoadBalanced
↓
识别 user-service
↓
DiscoveryClient
↓
Nacos 返回实例列表
↓
LoadBalancer 选择实例
↓
真实 IP:Port
↓
HTTP 请求

三十八、Nacos 和 LoadBalancer 分工

一定记:

Nacos:
有哪些实例?

LoadBalancer:
选哪个实例?

三十九、多实例实验

启动:

user-service:8081

再复制 Run Configuration:

user-service:8083

第二个启动参数:

--server.port=8083

两者都保持:

spring:
  application:
    name: user-service

四十、为什么服务名必须相同

你要的是:

同一个 user-service
的两个实例

不是:

两个不同服务

四十一、Nacos 中应该看到

user-service

下面两个实例:

127.0.0.1:8081
127.0.0.1:8083

四十二、为了观察实例

UserController 加:

@Value("${server.port}")
private Integer port;

返回:

return new UserVO(
    id,
    "用户-" + id
    + "-来自端口-"
    + port
);

反复访问:

order-service

观察响应来自:

8081 / 8083

四十三、什么叫客户端负载均衡

实例选择发生在:

Consumer

也就是:

order-service

这一侧。

所以叫:

客户端负载均衡

四十四、现在为什么还要 Feign

虽然已经可以:

restTemplate.getForObject(
    "http://user-service/users/" + id,
    UserVO.class
)

但业务多了以后还是很麻烦:

拼 URL
指定 Method
拼查询参数
处理 Body
处理响应类型

所以我们需要:

OpenFeign

四十五、OpenFeign 是什么

Feign 是:

声明式 HTTP 客户端

核心思想:

定义 Java 接口
↓
使用注解描述远程 HTTP API
↓
Spring 生成代理对象
↓
调用接口方法
↓
自动发 HTTP 请求

四十六、当前官方状态要知道

Spring 官方目前将 Spring Cloud OpenFeign 定位为:

feature-complete

也就是:

功能已经比较成熟
后续主要以维护和 Bug 修复为主

Spring 官方新方向更推荐:

Spring HTTP Service Clients

但是:

OpenFeign 在大量现有 Spring Cloud
和若依 Cloud 项目里仍然非常常见

所以:

这门课依然必须掌握 Feign

四十七、order-service 引入 Feign

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>
        spring-cloud-starter-openfeign
    </artifactId>
</dependency>

同样:

不要自己写版本

交给:

Spring Cloud BOM

管理。


四十八、启动类开启 Feign 扫描

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderServiceApplication {

    public static void main(
            String[] args
    ) {
        SpringApplication.run(
            OrderServiceApplication.class,
            args
        );
    }
}

四十九、创建 UserClient

@FeignClient(
    name = "user-service"
)
public interface UserClient {

    @GetMapping(
        "/users/{id}"
    )
    UserRemoteDTO getById(
        @PathVariable("id")
        Long id
    );
}

五十、@FeignClient name 到底是什么

name = "user-service"

这里必须对应 Provider:

spring:
  application:
    name: user-service

也就是说:

Feign 用服务名寻找 Provider

五十一、内部服务不要随便写 url

错误示例:

@FeignClient(
    name = "user-service",
    url = "http://localhost:8081"
)

这样:

地址又写死了

会绕过:

基于服务名的发现式调用

五十二、什么时候 url 合理

例如:

第三方天气 API
第三方地图 API
第三方短信 API

它们:

根本不会注册到你的 Nacos

这时:

固定 URL / 配置 URL

很合理。


五十三、发现式 Feign

内部服务:

@FeignClient(
    name = "user-service"
)

不要固定 URL。

Spring Cloud 会结合:

服务发现
+
LoadBalancer

选择真实实例。


五十四、OrderService 改成 Feign

@Service
public class OrderService {

    private final UserClient
            userClient;

    public OrderService(
            UserClient userClient
    ) {
        this.userClient =
                userClient;
    }

    public OrderVO getById(
            Long orderId
    ) {

        Long userId = 1L;

        UserRemoteDTO user =
                userClient.getById(
                    userId
                );

        OrderVO vo =
                new OrderVO();

        vo.setOrderId(orderId);
        vo.setUserId(userId);
        vo.setUserName(
            user.getName()
        );
        vo.setProductName(
            "机油套餐"
        );

        return vo;
    }
}

Controller:

@RestController
@RequestMapping("/orders")
public class OrderController {

    private final OrderService
            orderService;

    public OrderController(
            OrderService orderService
    ) {
        this.orderService =
                orderService;
    }

    @GetMapping("/{id}")
    public OrderVO getById(
            @PathVariable Long id
    ) {
        return orderService
            .getById(id);
    }
}

五十五、为什么这样分层

推荐:

Controller
↓
Service
↓
Feign Client

Controller:

负责 HTTP

Service:

负责业务协调

Feign Client:

负责远程接口契约

不要把:

大量 Feign 调用

直接堆在 Controller 里。


五十六、Feign 看起来像本地方法

现在:

userClient.getById(id);

非常像:

userService.getById(id);

但本质完全不同。

本地:

JVM 方法调用

Feign:

网络 HTTP 调用

所以 Feign 仍然可能:

超时
连接失败
服务宕机
404
500
网络抖动

五十七、Feign 大致执行过程

调用 UserClient.getById
↓
Feign 动态代理
↓
读取 @GetMapping
↓
读取 @PathVariable
↓
根据 user-service 找服务实例
↓
LoadBalancer 选实例
↓
发送 HTTP GET
↓
Provider 返回 JSON
↓
反序列化 UserRemoteDTO
↓
返回调用方

五十八、Feign 和 MyBatis Mapper 很像

MyBatis:

public interface UserMapper

没有自己写实现类,

框架:

创建代理

Feign:

public interface UserClient

也没有自己写实现类,

Spring:

创建 HTTP 代理

可以记:

Mapper Interface
→ SQL Proxy

Feign Interface
→ HTTP Proxy

五十九、@PathVariable

Provider:

@GetMapping("/users/{id}")
public UserVO getById(
        @PathVariable Long id
) {
}

Feign:

@GetMapping("/users/{id}")
UserRemoteDTO getById(
    @PathVariable("id")
    Long id
);

课程推荐:

显式写参数名

减少编译参数名差异造成的问题。


六十、@RequestParam

Provider:

@GetMapping("/users/search")
public List<UserVO> search(
        @RequestParam String name
) {
}

Feign:

@GetMapping("/users/search")
List<UserRemoteDTO> search(
    @RequestParam("name")
    String name
);

六十一、@RequestBody

Provider:

@PostMapping("/users")
public UserVO create(
        @RequestBody UserCreateDTO dto
) {
}

Feign:

@PostMapping("/users")
UserRemoteDTO create(
    @RequestBody
    UserCreateDTO dto
);

六十二、为什么远程 DTO 很重要

不要让 order-service:

直接依赖 user-service 数据库 Entity

例如 User Entity 可能有:

password
deleted
status
createTime
updateTime
内部字段

order-service 根本:

不应该知道

六十三、创建 UserRemoteDTO

public class UserRemoteDTO {

    private Long id;

    private String name;

    // getter / setter
}

只暴露:

远程调用真正需要的数据

六十四、远程 DTO 是接口契约

它表达:

user-service 愿意给其他服务什么数据

不是:

user 表所有字段

六十五、项目大以后可以抽 api 模块

例如:

spring-cloud-demo
├─ user-api
├─ user-service
└─ order-service

user-api:

UserClient
UserRemoteDTO
远程接口公共常量

六十六、user-api 不应该放什么

不要放:

UserMapper
UserServiceImpl
数据库连接
User Entity 内部实现

API 模块目标:

只描述“别人怎么调用我”

六十七、为什么很多若依 Cloud 项目有 remote 模块

后面你会看到:

RemoteUserService
RemoteFileService
RemoteLogService

本质就是:

跨服务接口契约

学完 Feign 后就很好理解。


六十八、@EnableFeignClients 扫描问题

如果 UserClient 不在:

启动类默认包扫描范围

可能:

Spring 找不到 Feign Bean

可以:

@EnableFeignClients(
    basePackages =
        "com.example.api"
)

六十九、常见错误

No qualifying bean of type UserClient

排查:

1. 是否引入 OpenFeign Starter
2. 是否 @EnableFeignClients
3. Client 是否被扫描
4. Client 是否加 @FeignClient

七十、Feign 404

如果:

服务实例已经找到

但是返回:

404

通常先检查:

Feign 路径
Controller 路径

例如 Provider:

@RequestMapping("/users")
@GetMapping("/{id}")

完整:

/users/{id}

Feign 如果写:

/user/{id}

就:

404

七十一、Feign 405

HTTP Method 不一致。

例如 Provider:

POST

Feign:

GET

会:

405 Method Not Allowed

七十二、Feign 400

通常检查:

@RequestParam
@PathVariable
@RequestBody
JSON 格式
参数名

七十三、Feign 503 / 无实例

先看:

Nacos 是否有该服务
服务名是否一致
Namespace 是否一致
实例是否健康
LoadBalancer 是否存在

七十四、服务名拼写是高频 Bug

Provider:

spring:
  application:
    name: user-service

Feign:

@FeignClient(
    name = "users-service"
)

只多一个:

s

就:

找不到服务

七十五、服务命名建议

统一:

全小写
使用短横线
业务语义清楚

例如:

user-service
order-service
travel-service

七十六、Feign 超时

远程调用必须有:

时间边界

当前 Spring Cloud OpenFeign 支持:

connectTimeout
readTimeout

七十七、全局默认超时

spring:
  cloud:
    openfeign:
      client:
        config:
          default:
            connectTimeout: 3000
            readTimeout: 5000
            loggerLevel: basic

七十八、指定 user-service

spring:
  cloud:
    openfeign:
      client:
        config:
          user-service:
            connectTimeout: 2000
            readTimeout: 3000

七十九、connectTimeout

简单理解:

建立连接允许等待多久

八十、readTimeout

连接建立后:

等待远程响应允许多久

八十一、为什么不能超时设置特别大

例如:

60 秒

如果下游挂了:

大量线程都等 60 秒

调用方很容易:

被拖垮

八十二、为什么也不能特别小

例如:

20ms

正常网络波动:

都可能超时

正确做法:

根据接口历史延迟和业务要求

确定。

课程:

先用几秒级测试

八十三、超时实验

Provider:

@GetMapping("/slow/{id}")
public UserVO slow(
        @PathVariable Long id
)
        throws InterruptedException {

    Thread.sleep(5000);

    return new UserVO(
        id,
        "slow-user"
    );
}

Feign:

readTimeout = 1000

调用:

应该触发读取超时

八十四、Feign 重试不能死背

不同 Feign / Spring Cloud 版本:

默认 Retryer 行为可能不同

不要永久记:

“Feign 默认一定重试 N 次”

正确工程思路:

如果要重试
先分析接口是否幂等

八十五、为什么写操作重试危险

例如:

POST /orders

第一次:

服务端已经创建成功

但网络响应:

丢了

Consumer 认为失败:

再次重试

如果没有幂等:

可能创建两个订单

八十六、查询接口更容易重试

例如:

GET /users/1

一般:

没有写副作用

相比 POST:

更容易安全重试

八十七、幂等手段

常见:

业务唯一号
数据库 UNIQUE
状态机
幂等 Token
请求去重

八十八、Feign 日志

调试时可以:

spring:
  cloud:
    openfeign:
      client:
        config:
          user-service:
            loggerLevel: full

logging:
  level:
    com.example.order.client.UserClient: DEBUG

八十九、日志级别

常见:

NONE
BASIC
HEADERS
FULL

九十、FULL 适合本地调试

生产不建议:

长期全部 FULL

原因:

日志很多
可能输出请求 Header
可能输出敏感数据

九十一、远程 N+1

一个非常重要的微服务性能问题。

错误:

for (
    Order order
    :
    orders
) {

    UserRemoteDTO user =
            userClient.getById(
                order.getUserId()
            );
}

如果订单:

100 条

就:

100 次 Feign 请求

九十二、为什么远程 N+1 更危险

数据库 N+1:

多次 SQL

远程 N+1:

多次网络请求
+
下游服务压力
+
序列化
+
连接

成本更高。


九十三、优化成批量接口

user-service:

POST /users/batch

请求:

[1,2,3,4]

返回:

[
  {
    "id": 1,
    "name": "张三"
  },
  {
    "id": 2,
    "name": "李四"
  }
]

九十四、Consumer 组装 Map

Map<Long, UserRemoteDTO>
    userMap =
        users.stream()
            .collect(
                Collectors.toMap(
                    UserRemoteDTO::getId,
                    Function.identity()
                )
            );

然后:

按 userId O(1) 匹配

九十五、批量接口也要有限制

不要:

一次传 100 万 ID

可以限制:

100
500
1000

根据业务决定。


九十六、Nacos Namespace

Namespace 可以理解:

大隔离空间

最常见:

dev
test
prod

九十七、为什么环境隔离非常重要

不希望:

本地 order-service
误调用 production user-service

所以开发、测试、生产:

应该隔离

九十八、Discovery Namespace 配置

spring:
  cloud:
    nacos:
      discovery:
        namespace: YOUR_NAMESPACE_ID

注意:

通常填写 Namespace ID

不是:

控制台显示名称

九十九、这是高频错误

控制台:

名称:
dev

ID:
83f3xxxx-xxxx

配置通常应写:

83f3xxxx-xxxx

一百、Group

Namespace 内还可以继续:

分组

默认常见:

DEFAULT_GROUP

例如:

Namespace:
dev

Group:
TRAVEL_GROUP

一百零一、Namespace 和 Group

简单记:

Namespace
= 大隔离

Group
= 大隔离里面继续分组

一百零二、Cluster

服务发现还有:

cluster-name

例如:

BEIJING
SHANGHAI

课程现在:

先认识

以后做:

就近调用
灰度

时会用到更多。


一百零三、Metadata

实例可以携带:

metadata

例如:

spring:
  cloud:
    nacos:
      discovery:
        metadata:
          version: v1

以后可以用于:

灰度路由
版本识别
实例筛选

一百零四、Nacos Discovery 不等于 Sentinel

Nacos 官方当前文档强调:

Service Discovery
主要回答:
“这个服务当前有哪些可用实例?”

它不是:

完整流量治理引擎

所以:

服务发现 → Nacos
流量保护 → Sentinel

一百零五、现在进入 Nacos Config

Nacos 除了:

服务注册发现

还可以:

集中配置

一百零六、为什么需要配置中心

一个单体:

1 个 application.yml

20 个微服务:

20 个 application.yml

例如数据库地址改变:

每个服务都改

非常麻烦。


一百零七、Nacos Config Starter

加入:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>
        spring-cloud-starter-alibaba-nacos-config
    </artifactId>
</dependency>

一百零八、最重要版本变化:spring.config.import

当前 Spring Cloud Alibaba 2025.x 推荐:

Spring Boot Config Data

也就是:

spring.config.import

一百零九、不要把 bootstrap.yml 当新项目主线

旧教程常见:

bootstrap.yml

然后:

spring.cloud.nacos.config...

但当前 SCA 已经向:

spring.config.import

迁移。

特别是:

2025.1.x

已经正式取消 Spring Cloud Bootstrap 的 Nacos 接入方式。


一百一十、application.yml 示例

spring:
  application:
    name: user-service

  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

      config:
        server-addr: 127.0.0.1:8848
        group: DEFAULT_GROUP

  config:
    import:
      - optional:nacos:user-service.yml?refreshEnabled=true

一百一十一、这里最关键

spring:
  config:
    import:

不是:

spring.cloud.config.import

注意层级。


一百一十二、DataId

user-service.yml

就是:

一份 Nacos 配置名称

一百一十三、Group

例如:

DEFAULT_GROUP

一百一十四、Namespace

例如:

dev Namespace ID

一百一十五、一份配置可以理解为

Namespace
+
Group
+
DataId

共同定位。


一百一十六、在 Nacos Console 新建配置

Data ID:

user-service.yml

Group:

DEFAULT_GROUP

配置内容:

user:
  greeting: Hello from Nacos

一百一十七、Java 读取

@RestController
@RefreshScope
public class ConfigController {

    @Value(
        "${user.greeting}"
    )
    private String greeting;

    @GetMapping(
        "/config/greeting"
    )
    public String greeting() {
        return greeting;
    }
}

一百一十八、refreshEnabled

?refreshEnabled=true

表示:

监听配置变化

一百一十九、修改配置

Nacos:

user:
  greeting: Hello V2

发布。

再访问:

/config/greeting

观察:

是否无需重启更新

一百二十、如果动态刷新没生效

排查:

refreshEnabled=true

DataId

Group

Namespace

配置是否发布

应用是否成功 import

Bean 是否需要 @RefreshScope

一百二十一、optional:nacos 是什么意思

配置:

spring:
  config:
    import:
      - optional:nacos:user-service.yml

optional 表示:

外部配置不存在或暂时无法读取时
应用允许继续启动

如果写:

spring:
  config:
    import:
      - nacos:user-service.yml

则:

配置加载失败
可能直接导致应用启动失败

一百二十二、什么时候用 optional

本地开发:

可以方便一些

但是关键生产配置:

是否允许缺失
需要按业务决定

例如数据库连接配置如果必须来自 Nacos:

就不应该随意 optional

一百二十三、optional 的调试陷阱

如果:

DataId 写错

但你用了:

optional:nacos

应用可能:

仍然启动成功

然后你误以为:

Nacos 配置已经加载

所以调试关键配置时可以:

暂时去掉 optional

让错误尽早暴露。


一百二十四、为什么现在不建议依赖 application.name 自动加载配置

Spring Cloud Alibaba 当前文档已经说明:

2023.0.1.3+

旧的:

shared-configs
extension-configs
默认 application.name 配置加载

已逐步废弃。

推荐:

spring.config.import

显式声明。


一百二十五、显式 import 的好处

一眼能看到:

当前服务依赖哪些配置

例如:

spring:
  config:
    import:
      - nacos:common.yml
      - nacos:user-service.yml

一百二十六、公共配置

多个服务都需要:

日志级别
公共业务开关
某些共享参数

可以放:

common.yml

一百二十七、服务专属配置

user-service 专属:

user-service.yml

order-service 专属:

order-service.yml

一百二十八、不要同一个 Key 到处定义

例如:

server.port

同时存在:

application.yml

common.yml

user-service.yml

很容易:

自己都不知道最终谁覆盖谁

一百二十九、配置原则

建议:

本地 application.yml
→ 启动 Nacos 必需的最小配置

common.yml
→ 多服务真正共享配置

service.yml
→ 单服务业务配置

一百三十、为什么 Nacos 地址通常本地保留

因为如果:

Nacos 地址本身

也只存放在 Nacos 中:

应用根本不知道去哪里找 Nacos

这是:

鸡生蛋问题

一百三十一、Discovery 与 Config Namespace 是两套配置

Discovery:

spring:
  cloud:
    nacos:
      discovery:
        namespace: YOUR_NAMESPACE_ID

Config:

spring:
  cloud:
    nacos:
      config:
        namespace: YOUR_NAMESPACE_ID

一百三十二、为什么经常忘一边

例如:

Discovery 在 dev
Config 还在 public

结果:

服务注册发现正常
但配置读取失败

反过来也可能发生。


一百三十三、建议统一环境变量

spring:
  cloud:
    nacos:
      discovery:
        namespace: ${NACOS_NAMESPACE:}
      config:
        namespace: ${NACOS_NAMESPACE:}

这样:

不同环境统一从外部注入

一百三十四、Nacos 服务发现常见配置

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        namespace: YOUR_NAMESPACE_ID
        group: DEFAULT_GROUP
        cluster-name: DEFAULT
        weight: 1

一百三十五、weight 是什么

实例权重。

例如:

实例 A:100

实例 B:20

某些实例选择策略可以:

更倾向选择 A

课程先理解:

权重用于服务实例选择倾向

一百三十六、不要把 Nacos Weight 当完整灰度发布

完整灰度还可能需要:

版本 Metadata
Gateway 路由
调用策略
用户分流

这里只学基础。


一百三十七、多网卡问题

你的电脑可能同时有:

Wi-Fi
VMware
WSL
Docker
VPN

Nacos Client 自动选择本机 IP 时:

可能注册成错误网卡地址

一百三十八、多网卡问题表现

Nacos 中看到:

192.168.xxx.xxx

但是 Consumer:

访问不到

一百三十九、本地练习可指定 IP

spring:
  cloud:
    nacos:
      discovery:
        ip: 127.0.0.1

仅适合:

同一台电脑本地练习

一百四十、生产为什么不能随便 127.0.0.1

其他服务器调用:

127.0.0.1

表示:

它自己

不是 Provider。

生产必须注册:

其他服务真正可达的 IP/地址

一百四十一、Nacos HealthIndicator

Spring Cloud Alibaba 2025.0.x 起:

Nacos Config / Discovery HealthIndicator
默认关闭

需要时:

显式启用

例如 Discovery:

spring:
  cloud:
    nacos:
      discovery:
        health-indicator:
          enabled: true

一百四十二、为什么不建议新手现在就全开

健康检查本身:

也会访问 Nacos

生产如果错误地把外部依赖健康作为:

Kubernetes liveness

可能放大故障。

课程本地:

先理解,不需要先开启

一百四十三、Nacos 挂了是不是所有服务马上全挂

不能简单回答:

是

客户端已经获取的实例信息:

通常会有本地缓存/订阅状态

已有服务在一段时间内:

可能仍能继续调用

但是:

新实例注册
实例变更
配置发布
配置刷新

会受影响。

所以生产 Nacos:

仍需要高可用

一百四十四、Feign 固定 URL 与服务发现调用

固定 URL

@FeignClient(
    name = "weather-client",
    url = "${weather.url}"
)

适合:

第三方 API

一百四十五、服务发现模式

@FeignClient(
    name = "user-service"
)

适合:

内部注册中心服务

一百四十六、为什么不要混淆

如果内部服务设置固定:

url

你可能发现:

Nacos 明明两个实例
却永远只调用一个地址

因为:

你绕过了服务发现式 URL

一百四十七、完整 order-service 依赖

<dependencies>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>
            spring-boot-starter-web
        </artifactId>
    </dependency>

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>
            spring-cloud-starter-alibaba-nacos-discovery
        </artifactId>
    </dependency>

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>
            spring-cloud-starter-alibaba-nacos-config
        </artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>
            spring-cloud-starter-loadbalancer
        </artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>
            spring-cloud-starter-openfeign
        </artifactId>
    </dependency>

</dependencies>

一百四十八、为什么没有 version

父工程:

BOM

已经负责版本管理。


一百四十九、如果 Maven 提示 version missing

先检查:

父 POM dependencyManagement

模块 parent

BOM 是否真正加载

Maven 是否 Reload

不要第一反应:

自己给每个 Starter 填版本

一百五十、Maven 刷新

IDEA:

右侧 Maven
↓
Reload All Maven Projects

命令:

mvn clean package

一百五十一、依赖树

排查版本:

mvn dependency:tree

微服务里:

必须会

因为大量:

NoSuchMethodError
ClassNotFoundException

其实都是依赖问题。


一百五十二、NoSuchMethodError

含义常常是:

编译时看到一个版本的方法
运行时加载了另一个不兼容版本

优先排:

BOM
手动覆盖版本
依赖冲突

不要先:

重装 IDEA

一百五十三、ClassNotFoundException

重点检查:

缺 Starter
依赖被 exclude
组件在新版本拆包
版本不兼容

一百五十四、不要混入老 Ribbon

旧教程可能:

Ribbon

现代 Spring Cloud 主线:

Spring Cloud LoadBalancer

所以当前课程:

不再以 Ribbon 为主线

一百五十五、也不要混老 Hystrix

旧教程常见:

Hystrix

本课程 Spring Cloud Alibaba:

Sentinel

作为流量保护重点。


一百五十六、为什么旧教程特别危险

你搜索:

Nacos Feign 教程

可能搜到:

2019
Spring Boot 2.1
Ribbon
Hystrix
bootstrap.yml

然后放进:

Spring Boot 3.5

非常容易:

启动报错

一百五十七、看教程第一件事

先看:

发布日期
JDK
Spring Boot
Spring Cloud
Spring Cloud Alibaba

一百五十八、Feign RequestInterceptor

以后做微服务身份传递时经常遇到:

RequestInterceptor

例如:

@Bean
public RequestInterceptor
    requestInterceptor() {

    return template -> {
        // 添加受控 Header
    };
}

一百五十九、为什么现在不写“透传全部请求头”

因为:

客户端 Header 不一定全部可信

内部调用应该明确:

哪些 Header 可以传
哪些身份信息应该重新校验

后面若依 Cloud:

会看到完整做法

一百六十、服务发现不是授权

一定区分:

Discovery
=
我知道 user-service 在哪里

Authorization
=
我是否有权调用某个业务能力

Nacos:

不会自动替你解决 RBAC

一百六十一、Feign 不会自动解决权限

order-service 能调用:

user-service

不代表它可以:

绕过所有用户权限

业务服务仍然需要:

认证
授权
内部调用安全

一百六十二、Feign 不会自动解决事务

@Transactional
public void createOrder() {

    orderMapper.insert(...);

    userClient.xxx();
}

order-service 本地事务可以控制:

order_db

但不能自动控制:

user-service 的数据库事务

一百六十三、跨服务事务例子

user-service:

先成功修改了数据

随后 order-service:

最后抛异常

order-service:

只能回滚自己的本地事务

user-service 已提交的:

不会自动跟着回滚

一百六十四、这就是为什么后面要学

分布式事务
最终一致性
MQ

一百六十五、Feign 返回 null 的错误写法

错误:

try {
    return userClient.getById(id);
} catch (Exception e) {
    return null;
}

后面:

NullPointerException

而且:

根因被吞掉

一百六十六、正确异常处理思想

至少记录:

远程服务
接口
业务 ID
异常类型

然后判断:

这个业务能不能降级

下一课 Sentinel 会继续处理。


一百六十七、Nacos Config 安全

生产配置中心可能存:

数据库地址
Redis 地址
业务配置

如果包含:

密码
SecretKey
Token

需要更加严格:

认证
权限
Secret 管理

不要认为:

放进 Nacos 就自动安全

一百六十八、Nacos 生产网络

官方明确建议:

Nacos 作为内部基础设施
部署在隔离网络

不要:

直接暴露公共互联网

一百六十九、配置变更也需要审计

为什么?

因为生产事故不一定来自:

代码发布

也可能来自:

改错 Nacos 配置

所以企业里配置也要:

权限
Review
审计
变更记录

一百七十、哪些配置适合动态刷新

例如:

业务开关
推荐数量
超时阈值
展示文案

一百七十一、哪些不要随便热改

例如:

数据库 Schema
核心线程模型
复杂连接池参数

是否能安全热更新:

要看对应 Bean 实现

一百七十二、@RefreshScope 不是魔法

它解决:

Spring Bean 配置刷新

但不代表:

所有第三方连接和业务状态
都能安全无缝热更新

一百七十三、完整调试顺序

远程调用失败时:

第一步:
Provider 自己能不能访问

第二步:
Provider 有没有注册到 Nacos

第三步:
Consumer 能不能发现 Provider

第四步:
LoadBalancer 有没有实例

第五步:
Feign 路径和参数是否正确

第六步:
下游业务自己是否报错

一百七十四、排错口诀

先 Provider
再 Nacos
再 Consumer
再 Feign

不要:

四层一起乱改

一百七十五、测试一:Provider 独立访问

GET
http://localhost:8081/users/1

必须:

先成功

一百七十六、测试二:关闭 Provider

再调:

order-service

亲自观察:

远程调用失败

建立:

网络调用不可靠

意识。


一百七十七、测试三:Nacos 注册

启动两个服务。

控制台:

user-service
order-service

都应该出现。


一百七十八、测试四:DiscoveryClient

discoveryClient.getInstances(
    "user-service"
);

确认:

实例列表不为空

一百七十九、测试五:LoadBalancer

将 URL 从:

localhost:8081

改成:

user-service

成功。


一百八十、测试六:两个 Provider

8081
8083

确认 Nacos:

同服务 2 个实例

一百八十一、测试七:Feign

移除业务里的:

RestTemplate 拼 URL

改:

userClient.getById(id);

一百八十二、测试八:服务名故意写错

@FeignClient(
    name = "users-service"
)

观察:

无实例异常

再改回来。


一百八十三、测试九:路径故意写错

Provider:

/users/{id}

Feign 故意:

/user/{id}

观察:

404

一百八十四、测试十:Method 写错

Provider POST,

Feign GET。

观察:

405

一百八十五、测试十一:Read Timeout

Provider sleep:

5 秒

Feign readTimeout:

1 秒

观察:

Read timed out

一百八十六、测试十二:Namespace 隔离

user-service:

dev

order-service:

test

观察:

互相发现不到

一百八十七、测试十三:Nacos Config

创建:

user-service.yml

配置:

user:
  greeting: Hello

读取成功。


一百八十八、测试十四:动态刷新

修改:

Hello
→
Hello V2

验证:

无需重启

一百八十九、测试十五:关一个实例

两个 user-service:

关闭 8081

确认:

8083 仍能继续响应

一百九十、常见 Bug 总表

1. Nacos Console 看不到服务

检查:

Discovery Starter
server-addr
application.name
Namespace
启动日志
服务是否真的启动

2. Feign Bean 不存在

检查:

@EnableFeignClients
扫描包
OpenFeign Starter

3. Feign 404

检查:

路径

4. Feign 400

检查:

参数注解和 JSON

5. Feign 405

检查:

HTTP Method

6. Feign 503

检查:

服务发现 / 实例

7. Read timed out

检查:

下游慢
readTimeout
SQL
阻塞

8. 服务名 URL 无法解析

RestTemplate:

是否 @LoadBalanced

9. Config 不加载

检查:

spring.config.import
DataId
Group
Namespace

10. bootstrap.yml 失效

新版本:

使用 application.yml
+
spring.config.import

11. Namespace 不生效

检查:

Namespace ID

12. 多网卡注册错 IP

检查:

Nacos 实例地址

13. NoSuchMethodError

检查:

版本冲突 / BOM

14. 动态刷新不生效

检查:

refreshEnabled
@RefreshScope

15. 只有一个实例接请求

检查:

另一个是否注册/健康

一百九十一、IDEA 多服务运行技巧

打开:

View
→ Tool Windows
→ Services

建议 Run Configuration 命名:

user-service-8081
user-service-8083
order-service-8082

不要全部叫:

Application

否则控制台非常乱。


一百九十二、端口冲突

Windows:

netstat -ano | findstr 8081

或 Nacos:

netstat -ano | findstr 8848

再查:

tasklist | findstr PID

确认是什么进程后再处理。


一百九十三、不要因为端口冲突就直接杀所有 Java

可能:

其他服务也在跑

一百九十四、Maven 模块学习结构

spring-cloud-demo
├─ pom.xml
├─ user-service
│  ├─ pom.xml
│  └─ src
└─ order-service
   ├─ pom.xml
   └─ src

一百九十五、后续可以扩

spring-cloud-demo
├─ common
├─ user-api
├─ user-service
├─ order-service
└─ gateway

但:

不要第一天就建 20 个模块

一百九十六、为什么学习要逐层增加

如果一开始:

Nacos
Feign
Sentinel
Gateway
Redis
MQ
Security

一起上,

报错后:

根本不知道哪层错

一百九十七、最好的学习方式

每加入一个组件
先验证它解决的问题

一百九十八、Cursor 提示词:父工程

当前环境是 JDK17。

请按 Spring Boot 3 技术栈创建一个学习用 Spring Cloud Alibaba 父 Maven 工程。

要求:
1. 使用官方兼容矩阵中明确的 Boot 3.5.x / Spring Cloud 2025.0.x / Spring Cloud Alibaba 2025.0.x 组合
2. 父工程 packaging=pom
3. 使用 Spring Cloud BOM
4. 使用 Spring Cloud Alibaba BOM
5. 创建 user-service 和 order-service
6. 子模块 Starter 不手动覆盖版本
7. 暂时不要引入 Sentinel、Gateway、MQ

一百九十九、Cursor 提示词:固定 URL

先不要使用 Nacos 和 Feign。

实现:
user-service:8081
order-service:8082

user-service:
GET /users/{id}

order-service:
GET /orders/{id}

order-service 使用普通 RestTemplate
固定调用:
http://localhost:8081/users/{id}

目的:
让我先看到服务地址写死的问题。

不要提前优化。

二百、Cursor 提示词:接 Nacos

现在将 user-service 和 order-service 接入 Nacos Discovery。

要求:
1. spring-cloud-starter-alibaba-nacos-discovery
2. Nacos 地址 127.0.0.1:8848
3. application.name 分别为 user-service、order-service
4. 使用当前 Spring Boot 3 兼容方式
5. 不使用 Ribbon
6. 启动后 Nacos 能看到两个服务
7. 输出验证与失败排查步骤

二百零一、Cursor 提示词:LoadBalancer

请给 order-service 增加 Spring Cloud LoadBalancer。

要求:
1. 明确引入 spring-cloud-starter-loadbalancer
2. RestTemplate Bean 添加 @LoadBalanced
3. URL 从 localhost 改成 http://user-service/users/{id}
4. 解释服务名如何转换为真实实例
5. 不使用 Ribbon

二百零二、Cursor 提示词:Feign

请把 order-service 的 RestTemplate 远程调用改成 OpenFeign。

要求:
1. 引入 spring-cloud-starter-openfeign
2. @EnableFeignClients
3. @FeignClient(name="user-service")
4. 不填写固定 url
5. GET /users/{id}
6. @PathVariable 显式写参数名
7. 返回 UserRemoteDTO
8. 不依赖 user-service 数据库 Entity
9. Controller → Service → FeignClient 分层

二百零三、Cursor 提示词:Feign 超时

请使用当前 Spring Cloud OpenFeign 配置方式增加超时:

spring.cloud.openfeign.client.config

要求:
1. connectTimeout
2. readTimeout
3. 创建 sleep 测试接口
4. 解释两个超时区别
5. 不要把超时简单改得特别大

二百零四、Cursor 提示词:Nacos Config

请按 Spring Cloud Alibaba 2025.x 当前方式接入 Nacos Config。

要求:
1. spring-cloud-starter-alibaba-nacos-config
2. application.yml
3. 使用 spring.config.import
4. 不以 bootstrap.yml 作为新项目主方案
5. DataId=user-service.yml
6. Group=DEFAULT_GROUP
7. refreshEnabled=true
8. 用 @Value + @RefreshScope 演示动态刷新
9. 解释 optional:nacos

二百零五、Cursor 提示词:Namespace

请检查 Nacos Namespace。

分别检查:
1. Discovery Namespace
2. Config Namespace
3. 是否误填 Namespace Name 而不是 Namespace ID
4. Provider / Consumer 是否在同一 Discovery Namespace
5. Config DataId 是否处于正确 Namespace
6. Group 是否一致

先分析,不修改。

二百零六、Cursor 提示词:版本冲突

当前微服务项目出现:
NoSuchMethodError / ClassNotFoundException

请分析 pom.xml 和 mvn dependency:tree。

重点检查:
1. Spring Boot
2. Spring Cloud BOM
3. Spring Cloud Alibaba BOM
4. Nacos Client
5. OpenFeign
6. LoadBalancer
7. 是否手工覆盖 BOM 版本
8. 是否混入 Spring Boot 2 老组件

不要默认“全部升级最新版”,先找到冲突链。

二百零七、Cursor 提示词:远程 N+1

请检查 order-service 是否存在远程 N+1。

如果订单列表循环:
userClient.getById(userId)

请优化:
1. 收集 userIds
2. 去重
3. user-service 提供批量接口
4. 一次 Feign 请求
5. 返回 List<UserRemoteDTO>
6. 转 Map<Long, UserRemoteDTO>
7. 再组装订单 VO
8. 限制批量请求数量

二百零八、面试题 1:Nacos 是什么

答:

Nacos 是一个动态服务发现、配置管理和服务管理平台。

在 Spring Cloud Alibaba 中,
常用 Nacos Discovery 完成服务注册发现,
使用 Nacos Config 做集中配置管理。

调用方可以通过服务名动态获得可用实例,
避免写死 IP 和端口。

二百零九、面试题 2:服务注册和发现区别

答:

服务注册是 Provider 启动后把服务名、IP、端口等实例信息注册到 Nacos。

服务发现是 Consumer 根据服务名查询或订阅可用实例列表。

一个相当于“告诉别人我在哪里”,
一个相当于“找到我要调用的人”。

二百一十、面试题 3:Nacos 和 LoadBalancer 区别

答:

Nacos 负责提供有哪些可用服务实例。

LoadBalancer 负责从多个实例中选择一个进行实际调用。

可以记成:
Nacos 负责“有哪些”,
LoadBalancer 负责“选哪个”。

二百一十一、面试题 4:Feign 是什么

答:

OpenFeign 是声明式 HTTP 客户端。

开发者通过 Java 接口和 Spring MVC 注解描述远程 API,
Spring Cloud 运行时创建代理对象并发送 HTTP 请求。

Feign 简化了服务调用代码,
但本质仍然是网络请求。

二百一十二、面试题 5:为什么 Feign 内部服务不要固定 url

答:

内部服务已经注册到 Nacos 时,
写固定 url 会绕过基于服务名的动态发现和负载均衡。

使用 @FeignClient(name="user-service")
可以适应服务扩缩容和实例地址变化。

二百一十三、面试题 6:@EnableFeignClients 做什么

答:

它开启 Feign Client 接口扫描。

Spring 会找到 @FeignClient 接口并创建代理 Bean。

如果接口不在默认扫描范围,
可以通过 basePackages 指定扫描路径。

二百一十四、面试题 7:什么是客户端负载均衡

答:

Consumer 先通过服务发现获得多个 Provider 实例,
再在自己这一侧选择一个实例发送请求。

实例选择发生在调用方,
所以叫客户端负载均衡。

现代 Spring Cloud 常使用 Spring Cloud LoadBalancer。

二百一十五、面试题 8:为什么不再以 Ribbon 为主

答:

Ribbon 属于较老的 Netflix Spring Cloud 技术体系。

现代 Spring Cloud 已转向 Spring Cloud LoadBalancer。

Spring Boot 3 新项目不应机械复制老 Ribbon 教程。

二百一十六、面试题 9:Namespace 做什么

答:

Namespace 用于较大粒度的资源隔离。

最常见场景是隔离开发、测试、生产环境。

不同 Namespace 内的服务和配置可以互相隔离。

二百一十七、面试题 10:为什么 Namespace 经常配错

答:

客户端配置通常需要 Namespace ID,
而不是控制台显示名称。

另外 Discovery 和 Config 有独立的 Namespace 配置,
只配一边会造成一部分功能正常、另一部分失败。

二百一十八、面试题 11:DataId 和 Group 是什么

答:

DataId 可以理解为配置文件名称,例如 user-service.yml。

Group 是 Namespace 内进一步分组的维度。

配置中心可以通过 Namespace + Group + DataId
定位一份具体配置。

二百一十九、面试题 12:为什么使用 spring.config.import

答:

Spring Cloud Alibaba 新版本已经迁移到 Spring Boot Config Data 机制。

2023.0.1.3 及以上推荐使用 spring.config.import 显式导入 Nacos 配置,
2025.1.x 更取消了 bootstrap.yml 的 Nacos Bootstrap 接入方式。

新项目应优先使用 application.yml + spring.config.import。

二百二十、面试题 13:optional:nacos 是什么

答:

optional:nacos 表示 Nacos 配置缺失或暂时拉取失败时,
应用允许继续启动。

非 optional 的 nacos: 导入失败时,
应用可能直接启动失败。

是否 optional 应根据配置是否属于强依赖决定。

二百二十一、面试题 14:connectTimeout 和 readTimeout 区别

答:

connectTimeout 限制建立连接的等待时间。

readTimeout 限制连接成功后等待远程响应的时间。

它们对应网络调用的不同阶段。

二百二十二、面试题 15:为什么重试要考虑幂等

答:

服务端可能已经执行成功,
只是响应因为网络问题没有到达 Consumer。

Consumer 重试后同一写操作可能执行第二次。

所以订单、支付等接口需要通过业务唯一号、
状态机和数据库唯一约束保证幂等。

二百二十三、面试题 16:什么是远程 N+1

答:

查询一批业务数据后,
循环为每条数据调用一次远程服务,
就会产生远程 N+1。

例如 100 个订单调用 100 次 user-service。

常见优化是批量远程接口、缓存或合理的数据冗余。

二百二十四、面试题 17:Nacos 和 Sentinel 区别

答:

Nacos 主要负责服务发现、实例管理和配置管理。

Sentinel 主要负责限流、熔断降级和系统保护。

Nacos 解决“服务在哪里”,
Sentinel 解决“服务调用如何更稳定”。

二百二十五、面试题 18:为什么注册成功不代表调用一定成功

答:

注册中心只表示存在服务实例。

真正 HTTP 调用仍可能因为路径错误、参数错误、
网络故障、超时、数据库异常等失败。

服务发现只是调用链中的一个环节。

二百二十六、面试题 19:Nacos 挂了业务是否立刻全挂

答:

不能简单这样判断。

客户端通常有已经获取到的实例信息,
已有服务在一定情况下仍可短时间继续通信。

但新实例注册、实例变化同步和配置中心都会受影响,
因此生产仍需要高可用 Nacos。

二百二十七、面试题 20:为什么使用 BOM

答:

Spring Cloud 和 Spring Cloud Alibaba 有大量相互依赖组件。

如果手工指定不同版本,
容易出现 NoSuchMethodError、ClassNotFoundException 等兼容问题。

BOM 可以统一管理一套兼容版本组合。

二百二十八、本章知识树

Nacos + Feign
│
├─ Maven
│  ├─ Parent
│  ├─ Spring Cloud BOM
│  └─ SCA BOM
│
├─ Basic HTTP
│  ├─ RestTemplate
│  └─ Fixed URL
│
├─ Nacos Discovery
│  ├─ Service Registration
│  ├─ Service Discovery
│  ├─ DiscoveryClient
│  ├─ ServiceInstance
│  ├─ Namespace
│  ├─ Group
│  ├─ Cluster
│  └─ Metadata
│
├─ LoadBalancer
│  ├─ @LoadBalanced
│  ├─ Service Name
│  └─ Multiple Instances
│
├─ Feign
│  ├─ @EnableFeignClients
│  ├─ @FeignClient
│  ├─ PathVariable
│  ├─ RequestParam
│  ├─ RequestBody
│  ├─ Remote DTO
│  ├─ Timeout
│  ├─ Logging
│  └─ Remote N+1
│
├─ Nacos Config
│  ├─ Config Starter
│  ├─ spring.config.import
│  ├─ DataId
│  ├─ Group
│  ├─ Namespace
│  ├─ optional:nacos
│  ├─ refreshEnabled
│  └─ @RefreshScope
│
└─ Debug
   ├─ 400
   ├─ 404
   ├─ 405
   ├─ 503
   ├─ Timeout
   ├─ Wrong Namespace
   └─ Version Conflict

二百二十九、最重要的调用链

OrderService
↓
UserClient
↓
Feign Proxy
↓
LoadBalancer
↓
Nacos Discovery
↓
user-service 实例列表
↓
选择一个实例
↓
HTTP
↓
UserController

二百三十、最重要配置链

application.yml
↓
Nacos Server
↓
spring.config.import
↓
Namespace
↓
Group
↓
DataId
↓
Spring Environment
↓
@Value / ConfigurationProperties

二百三十一、本章最重要的 20 条原则

1. 先体验写死 URL,再学习 Nacos

2. spring.application.name 是微服务逻辑身份

3. Nacos 负责告诉你有哪些服务实例

4. LoadBalancer 负责从实例里选一个

5. Feign 简化 HTTP,不会把远程调用变成本地调用

6. 内部注册服务不要随便在 FeignClient 写固定 url

7. Provider 和 Consumer 服务名必须完全一致

8. FeignClient 必须正确扫描

9. Discovery 与 Config 是两个不同 Starter

10. Namespace 最常用于环境隔离

11. Namespace 通常配置 ID,不是显示名称

12. 新版 Nacos Config 优先 spring.config.import

13. 新项目不要继续以 bootstrap.yml 老教程为主

14. 远程调用必须设置合理超时

15. 自动重试前必须判断接口幂等性

16. 跨服务使用 Remote DTO,不共享数据库 Entity

17. 循环 Feign 调用要警惕远程 N+1

18. BOM 负责统一微服务依赖版本

19. NoSuchMethodError 优先检查依赖冲突

20. 服务发现、调用、流量治理、权限是不同问题

二百三十二、本章最终验收

学完以后,你应该能够:

1. 创建父 Maven 微服务工程

2. 配置 Spring Cloud BOM

3. 配置 Spring Cloud Alibaba BOM

4. 创建 user-service

5. 创建 order-service

6. 使用固定 URL 做远程调用

7. 安装启动 Nacos

8. 注册服务到 Nacos

9. 使用 DiscoveryClient

10. 获取 ServiceInstance

11. 配置 Spring Cloud LoadBalancer

12. 使用 @LoadBalanced

13. 使用服务名调用 Provider

14. 启动同服务多个实例

15. 验证负载均衡

16. 引入 OpenFeign

17. 使用 @EnableFeignClients

18. 使用 @FeignClient

19. 使用 @PathVariable

20. 使用 @RequestParam

21. 使用 @RequestBody

22. 设计 Remote DTO

23. 配置 connectTimeout

24. 配置 readTimeout

25. 调试 Feign 日志

26. 理解 Namespace

27. 理解 Group

28. 理解 DataId

29. 接入 Nacos Config

30. 使用 spring.config.import

31. 理解 optional:nacos

32. 使用 refreshEnabled

33. 使用 @RefreshScope

34. 排查服务注册失败

35. 排查 Feign 404/400/405/503

36. 排查多网卡 IP

37. 排查 Namespace 错误

38. 使用 mvn dependency:tree

39. 排查 NoSuchMethodError

40. 识别远程 N+1

二百三十三、当前官方版本提醒

截至本笔记整理时:

Nacos 官方站点最新稳定版显示:
3.2.4

发布时间:

2026-08-27

但是 Spring Cloud Alibaba 项目选择 Nacos 时:

不要只追 Nacos Server 最新版

还要检查:

Spring Cloud Alibaba 组件兼容矩阵

Spring Cloud Alibaba 官方 2025.0.x 基线:

SCA 2025.0.0.0
Spring Cloud 2025.0.0
Spring Boot 3.5.0
Nacos Client 3.0.3

而:

2025.1.x

已经进入:

Spring Boot 4.0.x

所以你当前:

JDK17 + Spring Boot 3

学习时不要误把:

Boot 4 示例

复制进 Boot 3 项目。


二百三十四、OpenFeign 当前官方提醒

Spring Cloud OpenFeign 当前仍有稳定版本维护。

但是 Spring 官方已明确表示:

OpenFeign 处于 feature-complete 状态

并建议新方向:

Spring HTTP Service Clients

你当前课程继续学 Feign 的原因:

1. Spring Cloud Alibaba 传统项目大量使用

2. 若依 Cloud 等项目大量能看到

3. 面试和维护旧/现有系统仍很常见

4. Feign 能很好帮助理解声明式远程调用

所以正确态度:

会 Feign
但知道 Spring 生态正在继续演进

二百三十五、下一课

按照课程表,下一篇进入:

《Spring Cloud Alibaba:Sentinel 详解》

下一课不再只是:

服务能不能找到

而是解决:

找到以后调用失败怎么办?
流量太大怎么办?
下游越来越慢怎么办?

我们会真正制造故障:

user-service 人为 sleep
↓
order-service 大量等待
↓
观察风险
↓
接入 Sentinel

继续学习:

QPS 限流

线程数限流

流控规则

BlockException

熔断降级

慢调用比例

异常比例

异常数

Fallback

热点参数限流

系统保护

Feign + Sentinel

也就是正式进入:

微服务稳定性治理

阶段。