Spring Cloud Config 基础:搭建 Config Server 与 Config Client

说实话,配置中心这玩意儿,在微服务架构里就是个「基础设施」。你想想看,几十个微服务,每个都本地配一套数据库地址、缓存地址……改个东西得一个个重启。

我刚接手公司第一个微服务改造项目时,就踩过这个坑。当时线上数据库切主,我凌晨三点爬起来改了二十多个服务的配置文件……嗯,从那以后,我坚决要把配置集中管理。

Spring Cloud Config 是 Spring Cloud 家族里最轻量级的配置中心方案。它不依赖数据库,直接挂 Git 仓库,说白了就是一个「通过 HTTP 读配置」的服务。这一章,我带你手搭一遍。

本章我们要做什么?

  • 搭建 Config Server —— 一个可以从 Git 仓库拉取配置的微服务。
  • 搭建 Config Client —— 一个可以从 Config Server 动态获取配置的普通微服务。
  • 验证两者的协作流程:客户端启动时拉取远程配置,并支持动态刷新。

先理清架构

先别急着写代码。Spring Cloud Config 的核心架构其实很简单,我画了张图,一目了然。

Spring Cloud Config 基础架构 Git 配置文件仓库 application.yml / user-service.yml Config Server Spring Boot + @EnableConfigServer Config Client A user-service Config Client B order-service Config Client C gateway-service pull 配置 GET /user-service/default GET /order-service/default GET /gateway-service/default 💡 关键点: Config Server 充当 Git 配置仓库的 HTTP 代理层。Client 不直接访问 Git,而是通过 Config Server 获取配置。

第一步:搭建 Config Server

先从服务端开始。创建一个 Spring Boot 项目,加两个关键依赖:

<!-- Config Server 核心依赖 -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-config-server</artifactId>
</dependency>

<!-- 必加:不然启动会报找不到 bootstrap -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

然后写启动类,就一行注解的事:

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

配置方面,application.yml 长这样:

server:
  port: 8888

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/your-org/config-repo.git
          search-paths: '{application}'
          default-label: main
          # 如果是私有仓库,需要配用户名密码或 SSH key
          username: ${GIT_USER}
          password: ${GIT_PASSWORD}
我个人习惯:Git URI 别写死,放到环境变量里。不同环境(开发/测试/生产)指向不同 Git 仓库或者不同分支,省得改 yml。

启动后,你可以先验证一下。浏览器访问:http://localhost:8888/user-service/default,如果 Git 仓库里确实有 user-service.yml,你会看到 JSON 返回值,里面包含配置内容。能拿到数据,说明服务端通了。

第二步:搭建 Config Client

客户端更简单。同样的 Spring Boot 项目,依赖只需要一个:

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

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

配置文件的写法有讲究。注意:Config Client 的配置不能写在 application.yml 里,要写在 bootstrap.yml 里。为什么?因为 bootstrap.yml 优先加载,在应用上下文初始化之前就把远程配置拉下来了。

# bootstrap.yml
spring:
  application:
    name: user-service
  cloud:
    config:
      uri: http://localhost:8888
      # 默认拉取 main 分支,也可以改
      label: main

---
# 不同环境切不同的 Config Server 地址
spring:
  config:
    activate:
      on-profile: dev
  cloud:
    config:
      uri: http://config-dev:8888

写一个简单的 Controller 验证配置是否生效:

@RestController
public class ConfigController {

    @Value("${user.max-login-attempts}")
    private int maxLoginAttempts;

    @GetMapping("/config")
    public Map<String, Object> config() {
        return Map.of("maxLoginAttempts", maxLoginAttempts);
    }
}

Git 仓库里对应的 user-service.yml 内容:

user:
  max-login-attempts: 5

启动客户端,访问 /config,如果返回 {"maxLoginAttempts":5},说明配置中心已经跑通了。

我曾经遇到的一个坑:客户端启动报 FetchError: Cannot clone or checkout repository。检查了半天,发现是 Git 仓库 URI 写错了。建议先在本地 git clone 测试一下你的 URI 和凭证是否正确,再配到 Server 里。

验证多环境配置

Spring Cloud Config 支持按 profiles 区分配置。你在 Git 仓库放这些文件:

  • user-service.yml —— 公共配置,所有环境共享。
  • user-service-dev.yml —— 开发环境覆盖。
  • user-service-prod.yml —— 生产环境覆盖。

客户端通过 spring.profiles.active=dev 启动时,Config Server 会自动合并配置:公共 + dev 覆盖。

表格:配置文件优先级

同一个属性出现在多个文件里,谁生效?我列个表格:

加载顺序 配置来源 示例
1(最低) Git 仓库中的 application.yml 所有应用共享的默认配置
2 Git 仓库中的 {application}.yml user-service.yml
3 Git 仓库中的 {application}-{profile}.yml user-service-dev.yml
4(最高) 客户端本地 application.yml 本地覆盖远程配置(慎用)

你想想看,这个优先级意味着什么?如果你在远程配置里设置了一个值,客户端本地又能覆盖它,那你改远程配置就没意义了。所以我建议:生产环境把客户端的覆盖功能关掉,强制所有配置走 Server。

动态刷新:不加此功能的配置中心是不完整的

现在配置能远程读了,但改完 Git 仓库后,客户端不会自动感知。你需要手动触发刷新。Spring Cloud Config 提供了 /actuator/refresh 端点。

首先,在客户端加依赖:

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

然后在 bootstrap.yml 里开放端点:

management:
  endpoints:
    web:
      exposure:
        include: refresh

使用时,改了 Git 仓库配置后,向客户端发一个 POST 请求:

curl -X POST http://localhost:8081/actuator/refresh

返回一个 JSON 数组,里面列出哪些属性被刷新了。注意,被 @Value 注入的字段必须是可变的,且类上要有 @RefreshScope 注解:

@RestController
@RefreshScope
public class ConfigController {

    @Value("${user.max-login-attempts}")
    private int maxLoginAttempts;

    // ...
}

关于刷新,我想说三点

  1. @RefreshScope 必须加在 Bean 上,不是加在属性上。你加错地方,刷新完值还是旧的。
  2. 刷新操作对当前节点有效,多实例需要逐个刷新或借助消息总线(Spring Cloud Bus,后面章节会讲)。
  3. 生产环境不要每次都手动刷新,配合 Git Webhooks 自动化触发,是常见做法。

避坑指南

  • Bootstrap 上下文未加载:检查是否加了 spring-cloud-starter-bootstrap 依赖。Spring Cloud 2020 版本之后,默认不加载 bootstrap。
  • Git 仓库权限问题:私仓推荐用 SSH 方式配置密钥,比 username/password 更安全。我在项目中遇到过密码过期导致 Config Server 启动失败的情况。
  • 配置值包含特殊字符:YAML 里如果有 #、!、% 这些符号,记得加引号,不然解析会报错。
  • 刷新后配置没变:检查 Bean 是否真的在 @RefreshScope 之下。有些 Bean 是通过 @Bean 方法创建的,那方法所在的 @Configuration 类也得加 @RefreshScope。

到这一步,你已经把 Spring Cloud Config 最核心的 Server-Client 通信链路搭好了。代码不长,但背后的机制值得理解:为什么用 bootstrap?为什么有优先级?为什么需要 refresh?