菜鸟的修仙笔记,一边学一边记,有不对的地方欢迎大佬指正


前言

最近在学 Spring Cloud 微服务体系,今天磕了一下午的 Nacos,从注册中心到配置中心,从服务发现到负载均衡再到配置自动刷新,总算把核心链路捋顺了。趁热打铁写篇博客,狠狠真实一下自己。

先放一张整体架构图,心里有个谱:

┌──────────────────────────────────────────────────────────┐
│                      Nacos Server                        │
│  ┌─────────────────────┐  ┌───────────────────────────┐  │
│  │    注册中心           │  │      配置中心             │  │
│  │  (服务注册 & 发现)    │  │  (配置管理 & 动态刷新)     │  │
│  └──────┬──────────────┘  └────────────┬──────────────┘  │
└─────────┼──────────────────────────────┼─────────────────┘
          │                              │
    ┌─────▼─────┐                  ┌─────▼─────┐
    │ order服务  │                  │ order服务 │
    │ 注册到Nacos│                  │ 拉取配置   │
    │ 发现user服务│                 │ 监听变化   │
    └─────┬─────┘                  └───────────┘
          │
    ┌─────▼─────┐
    │ LoadBal   │
    │ ancer     │
    │ 负载均衡   │
    └───────────┘

一、注册中心

1.1 什么是注册中心?

微服务架构下,服务实例的网络地址是动态变化的(扩缩容、故障转移),不可能把 IP 写死在代码里。注册中心就是所有服务的"通讯录":

  • 服务启动时向注册中心注册自己的地址

  • 服务调用时从注册中心发现目标服务的地址列表

1.2 引入 Nacos 注册中心

第一步:加依赖

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

第二步:配地址

spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848  # Nacos 服务地址
  application:
    name: order-service              # 服务名,注册时以此作为标识

第三步:启动类加注解

@SpringBootApplication
@EnableDiscoveryClient  // 开启服务发现
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

启动后去 Nacos 控制台 localhost:8848/nacos → 服务管理 → 服务列表,就能看到 order-service 了。


二、服务调用 & 负载均衡

注册只是第一步,真正关键的是怎么调用

2.1 RestTemplate + @LoadBalanced

最经典的方式,用 RestTemplate 发起 HTTP 调用,加 @LoadBalanced 注解开启负载均衡:

@Configuration
public class RestTemplateConfig {
​
    @Bean
    @LoadBalanced   // 核心:让 RestTemplate 拥有负载均衡能力
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

调用时直接用服务名代替 IP:端口:

@Service
public class OrderService {
​
    @Autowired
    private RestTemplate restTemplate;
​
    public User getUserById(Long id) {
        // 注意:URL 里写的是服务名,不是 IP!
        String url = "http://user-service/user/" + id;
        return restTemplate.getForObject(url, User.class);
    }
}

@LoadBalanced 做了什么事?

restTemplate.getForObject("http://user-service/user/1", User.class)
                        │
                        ▼
              ┌─────────────────┐
              │ 拦截器拦截请求   │  ← LoadBalancerInterceptor
              │ 从 URL 中提取服务名│
              └────────┬────────┘
                       │
                       ▼
              ┌─────────────────┐
              │ 从 Nacos 获取    │  ← NacosServerList
              │ user-service 的  │
              │ 所有实例列表      │
              │ [192.168.1.1:8081│
              │  192.168.1.2:8082│
              │  192.168.1.3:8083]│
              └────────┬────────┘
                       │
                       ▼
              ┌─────────────────┐
              │ 负载均衡算法选择  │
              │ 一个实例         │
              │ → 192.168.1.2:8082│
              └────────┬────────┘
                       │
                       ▼
              ┌─────────────────┐
              │ 替换 URL 为真实地址│
              │ http://192.168.1.2│
              │ :8082/user/1    │
              └─────────────────┘

2.2 LoadBalancerClient

除了用 @LoadBalanced 注解,也可以直接注入 LoadBalancerClient 手动选择实例:

@Service
public class OrderService {
​
    @Autowired
    private LoadBalancerClient loadBalancerClient;
​
    @Autowired
    private RestTemplate restTemplate;
​
    public User getUserById(Long id) {
        // 手动选择服务实例
        ServiceInstance instance = loadBalancerClient.choose("user-service");
        // 拿到真实的 host:port
        String url = String.format("http://%s:%s/user/%s",
                instance.getHost(), instance.getPort(), id);
        return restTemplate.getForObject(url, User.class);
    }
}

两种方式对比:

方式 优点 缺点
@LoadBalanced 代码简洁,注解驱动 不够灵活,无法自定义选择逻辑
LoadBalancerClient 灵活,可以拿到实例元数据 代码量稍多,需手动拼接 URL

三、配置中心

注册中心搞定了"服务在哪"的问题,配置中心解决的是"配置怎么管"的问题。

3.1 为什么需要配置中心?

痛点场景:

  • 10 个微服务,每个都有 application.yml,改个数据库地址要改 10 个文件

  • 改了配置文件要重启服务才能生效

  • 敏感信息(密码)散落在各个配置文件里,不安全

配置中心就是把所有配置统一管理、动态刷新的地方

3.2 接入 Nacos 配置中心

第一步:加依赖

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

第二步:配 Nacos 地址

spring:
  application:
    name: order-service
  cloud:
    nacos:
      config:
        server-addr: localhost:8848

第三步:在 Nacos 控制台创建配置

进入 Nacos → 配置管理 → 配置列表 → 创建配置:

字段 说明
Data ID order-service.yaml 默认规则:${spring.application.name}.${file-extension}
Group DEFAULT_GROUP 默认分组
配置内容 见下方
# Nacos 控制台里的配置内容
user:
  name: 张三
  age: 18

3.3 使用 spring.config.import 导入配置

Spring Boot 2.4+ 推荐用 spring.config.import 导入 Nacos 配置:

# application.yml
spring:
  config:
    import:
      - nacos:order-service.yaml    # 导入 Nacos 配置
      - nacos:common.yaml           # 可以导入多个
  cloud:
    nacos:
      config:
        server-addr: localhost:8848

引入多个 data-id 的场景:

order-service.yaml      ← 订单服务独有配置
common.yaml             ← 公共配置(各服务共享)
order-service-dev.yaml  ← 开发环境配置

四、配置取值 & 自动刷新

4.1 @Value + @RefreshScope

@RestController
@RefreshScope   // 关键:标记这个 Bean 的 @Value 字段需要动态刷新
public class TestController {

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

    @Value("${user.age}")
    private Integer age;

    @GetMapping("/user-info")
    public String getUserInfo() {
        return "姓名:" + userName + ",年龄:" + age;
    }
}

验证自动刷新:

  1. 访问 /user-info姓名:张三,年龄:18

  2. 去 Nacos 控制台把 user.age 改成 20,发布

  3. 再次访问 /user-info姓名:张三,年龄:20 ✅ 不用重启!

原理简述:

Nacos 配置变更
      │
      ▼
Nacos Client 收到通知
      │
      ▼
Spring Cloud 刷新 Context
      │
      ▼
@RefreshScope 标注的 Bean 被重建
      │
      ▼
@Value 重新从 Environment 取值 → 新值生效

4.2 @ConfigurationProperties 批量绑定

当配置项很多时,一个个 @Value 太累了,用 @ConfigurationProperties 批量绑定:

Nacos 配置:

user:
  name: 张三
  age: 18
  addr:
    province: 广东
    city: 深圳

配置类:

@Component
@ConfigurationProperties(prefix = "user")   // 绑定 user 开头的所有配置
@RefreshScope                              // 同样支持自动刷新!
@Data
public class UserProperties {

    private String name;
    private Integer age;
    }
}

使用:

@RestController
public class TestController {

    @Autowired
    private UserProperties userProperties;

    @GetMapping("/user-info")
    public String getUserInfo() {
        return userProperties.getName() + " - "
             + userProperties.getAddr().getProvince();
    }
}

@Value vs @ConfigurationProperties 对比:

维度 @Value @ConfigurationProperties
单个属性 ✅ 方便 也能用但杀鸡用牛刀
批量绑定 ❌ 要写很多个 ✅ 一个 prefix 搞定
嵌套对象 ❌ 不支持 ✅ 自动映射
松散绑定 ❌ 不支持 user-nameuserName
数据校验 ❌ 不支持 ✅ 可以用 @Validated
自动刷新 需要 @RefreshScope 需要 @RefreshScope

推荐: 单两个属性用 @Value,多了就用 @ConfigurationProperties

4.3 NacosConfigManager 监听配置变化

前面两种方式都是被动刷新,如果你想主动感知配置变化并做一些处理(比如重建连接池),可以用 NacosConfigManager 注册监听器:

@Component
public class ConfigListenerDemo {

    @Autowired
    private NacosConfigManager nacosConfigManager;

    @PostConstruct
    public void init() {
        // 获取 ConfigService
        ConfigService configService = nacosConfigManager.getConfigService();
        try {
            // 添加监听器:dataId, group, 回调
            configService.addListener("order-service.yaml", "DEFAULT_GROUP",
                    new Listener() {
                        @Override
                        public Executor getExecutor() {
                            return Executors.newSingleThreadExecutor();
                        }

                        @Override
                        public void receiveConfigInfo(String configInfo) {
                            // configInfo 就是最新的配置内容(yaml 原文)
                            System.out.println("配置变了!新配置:\n" + configInfo);

                            // 在这里做一些自定义逻辑
                            // 比如:重建数据库连接池、清空缓存等
                            onConfigChanged(configInfo);
                        }
                    });
        } catch (NacosException e) {
            e.printStackTrace();
        }
    }

    private void onConfigChanged(String newConfig) {
        // 自定义处理逻辑
    }
}

五、扩展:配置隔离 & 环境切换

5.1 配置的三级隔离

Nacos 通过三个维度实现配置隔离:

Namespace(命名空间)
  └── Group(分组)
        └── Data ID(配置集ID)
              └── 具体配置内容

5.2 Namespace —— 环境隔离

典型用法:用 namespace 区分开发/测试/生产环境。

spring:
  cloud:
    nacos:
      config:
        server-addr: localhost:8848
        namespace: dev-env-id-xxxx    # 用 namespace 的 ID

不同 namespace 的配置完全隔离,dev 环境看不到 prod 的配置。

5.3 Group —— 微服务分组

同一个 namespace 下,用 group 对不同微服务做逻辑分组:

Group: ORDER_GROUP
  ├── order-service.yaml
  └── order-db.yaml

Group: USER_GROUP
  ├── user-service.yaml
  └── user-db.yaml
spring:
  cloud:
    nacos:
      config:
        group: ORDER_GROUP   # 指定分组

5.4 Data ID —— 配置集拆分

利用 data-id 把一个大配置文件拆成多个小配置:

order-service.yaml          # 主配置
order-service-datasource.yaml  # 数据源配置
order-service-cache.yaml       # 缓存配置
order-service-mq.yaml          # 消息队列配置

这样改造数据源时只改 *-datasource.yaml,不会误触其他配置。

5.5 优先

配置优先级(由低到高):

  1. 共享配置(shared-configs)

  2. 扩展配置(extension-configs)

  3. 应用专属配置(${spring.application.name}.yaml

  4. 应用+环境配置(${spring.application.name}-${profile}.yaml

优先级高的会覆盖优先级低的同名配置,这就是 "近者优先" 原则。


六、完整 Demo 流程回顾

把今天学的串起来,实现一个完整调用链路:

User 请求 GET /order/1
        │
        ▼
  ┌─────────────┐     2. 从 Nacos 注册中心
  │ order-service│ ◄───   发现 user-service 的实例列表
  └──────┬──────┘
         │ 1. order-service 启动时
         │    从 Nacos 配置中心拉取配置
         │    注册自己到 Nacos
         │
         │ 3. 使用 @LoadBalanced RestTemplate
         │    或 LoadBalancerClient
         │    负载均衡选择一个 user-service 实例
         │
         ▼
  ┌─────────────┐
  │ user-service │
  │ 实例 2       │ ◄─── 4. 实际发起 HTTP 调用
  └─────────────┘
         │
         │ 5. user-service 处理请求
         │    读取的配置如果被修改
         │    @RefreshScope 自动刷新
         │
         ▼
     返回结果给 order-service → 返回给 User

关键依赖:

<!-- 注册中心 -->
<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>

总结

今天学的内容其实就两条主线:

注册中心 配置中心
核心问题 服务在哪? 配置怎么管?
关键注解 @EnableDiscoveryClient@LoadBalanced @RefreshScope@ConfigurationProperties
服务调用 RestTemplate / LoadBalancerClient -
负载均衡 轮询 → 可自定义 -
配置刷新 - @Value 刷新 / NacosConfigManager 监听
隔离体系 namespace → group → serviceName namespace → group → dataId

Nacos = 注册中心 + 配置中心

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐