一、核心概念

在微服务架构中,随着服务数量增多,将配置(如数据库连接、开关、超时时间)硬编码在代码中或分散在各个服务器的配置文件中变得难以管理、易出错、无法动态更新。配置中心(Config Server)解决了这个问题。

1. 配置中心(Config Server)是什么?

  • 集中化配置管理: 一个独立的服务,负责集中存储和管理所有微服务的配置信息。
  • 环境隔离: 支持为不同环境(dev, test, prod)维护独立的配置。
  • 版本控制: 配置通常存储在 Git 仓库中,天然支持版本控制、审计和回滚。
  • 动态刷新: 某些配置在运行时修改后,可以通知服务实例刷新(需配合 @RefreshScope)。
  • 安全: 可以对敏感配置(如密码)进行加密存储。

2. 核心组件

  • Config Server: 配置服务器。它从后端存储(如 Git)获取配置,并通过 REST API 暴露给 Config Client
  • Config Client: 配置客户端。微服务应用在启动时,会从 Config Server 获取自己的配置。
  • 后端存储 (Backend Store): 存储配置文件的地方。最常用的是 Git 仓库(支持版本控制),也支持本地文件系统、Subversion、Vault 等。
  • EnvironmentRepository: Config Server 内部的接口,用于从后端存储加载 Environment(包含属性源 PropertySources)。

3. 配置文件命名规则

Config Server 通过特定的命名规则来查找配置文件。规则为:{application}-{profile}.yml{application}-{profile}.properties

  • {application}: 通常是 spring.application.name 的值。例如 user-service
  • {profile}: 激活的 Spring Profile。例如 dev, prod, test
  • {label}: (可选) Git 仓库的分支或标签。默认是 mastermain

查找顺序 (重要):

  1. /{application}-{profile}.yml
  2. /{label}/{application}-{profile}.yml
  3. /{application}-{profile}.properties
  4. /{label}/{application}-{profile}.properties

示例:

  • user-service-dev.yml -> user-service 服务在 dev 环境的配置。
  • user-service-prod.yml -> user-service 服务在 prod 环境的配置。
  • application.yml -> 所有服务共享的默认配置。
  • application-dev.yml -> 所有服务在 dev 环境的共享配置。

二、操作步骤(非常详细 - 基于 Spring Cloud Config Server + Git + Nacos)

我们将创建一个 config-server,并让 user-serviceorder-service 作为 config-client 从它获取配置。

步骤 1:准备 Git 仓库(后端存储)

  1. 创建一个 Git 仓库:
    • 在 Gitee、GitHub 或本地创建一个新仓库,例如 spring-cloud-config-repo
  2. 创建配置文件:
    • 在仓库根目录下创建以下文件:
      • application.yml: (所有服务的公共配置)
        # application.yml
        server:
          port: 8080 # 默认端口,可被覆盖
        logging:
          level:
            root: INFO
        
      • user-service.yml: (user-service 的默认配置)
        # user-service.yml
        server:
          port: 8081
        
      • user-service-dev.yml: (user-service 在 dev 环境的配置)
        # user-service-dev.yml
        server:
          port: 8082 # dev 环境端口
        logging:
          level:
            com.example.userservice: DEBUG
        
      • user-service-prod.yml: (user-service 在 prod 环境的配置)
        # user-service-prod.yml
        server:
          port: 8083 # prod 环境端口
        logging:
          level:
            com.example.userservice: WARN
        
      • order-service.yml: (order-service 的默认配置)
        # order-service.yml
        server:
          port: 9081
        
      • order-service-dev.yml: (order-service 在 dev 环境的配置)
        # order-service-dev.yml
        server:
          port: 9082
        logging:
          level:
            com.example.orderservice: DEBUG
        
      • nacos-discovery.yml: (所有服务关于 Nacos 的公共配置)
        # nacos-discovery.yml
        spring:
          cloud:
            nacos:
              discovery:
                server-addr: 127.0.0.1:8848
                # namespace: your-prod-namespace-id # 不同环境可配置不同 namespace
                # group: DEFAULT_GROUP
        
  3. 提交并推送到远程仓库:
    git add .
    git commit -m "Initial config files"
    git push origin main
    

步骤 2:创建 Config Server (config-server)

  1. 创建项目:

    • 使用 Spring Initializr (https://start.spring.io/) 创建一个新项目。
    • Group: com.example
    • Artifact: config-server
    • Dependencies:
      • Spring Web
      • Spring Cloud Config Server (关键)
      • Spring Cloud Config Monitor (可选,用于 Webhook 自动刷新)
      • Nacos Discovery Client (可选,将 Config Server 注册到 Nacos)
  2. pom.xml 示例:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" ...>
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.7.14</version> <!-- 与你的 Spring Cloud 版本兼容 -->
            <relativePath/>
        </parent>
        <groupId>com.example</groupId>
        <artifactId>config-server</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>config-server</name>
    
        <properties>
            <java.version>8</java.version>
            <spring-cloud.version>2021.0.8</spring-cloud.version> <!-- Hoxton.SR12, 2021.x 等 -->
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-config-server</artifactId>
            </dependency>
            <!-- 可选:将 Config Server 注册到 Nacos -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            </dependency>
            <!-- 可选:Webhook 自动刷新 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-config-monitor</artifactId>
            </dependency>
        </dependencies>
    
        <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>
                <!-- 如果用 Nacos -->
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                    <version>2021.0.5.0</version> <!-- 与 Spring Cloud 版本兼容 -->
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    
  3. 主类 ConfigServerApplication.java:

    package com.example.configserver;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.config.server.EnableConfigServer; // 启用 Config Server
    import com.alibaba.cloud.nacos.NacosDiscoveryClient; // 如果用 Nacos
    
    @SpringBootApplication
    @EnableConfigServer // 核心注解,启用配置服务器功能
    public class ConfigServerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ConfigServerApplication.class, args);
        }
    }
    
  4. 配置 application.yml:

    # config-server/src/main/resources/application.yml
    server:
      port: 8888 # Config Server 自身的端口
    
    spring:
      application:
        name: config-server # 服务名
      cloud:
        config:
          server:
            git:
              uri: https://gitee.com/yourusername/spring-cloud-config-repo.git # 你的 Git 仓库地址
              username: your-git-username # 如果是私有仓库,需要用户名
              password: your-git-password-or-token # 如果是私有仓库,需要密码或 Personal Access Token (推荐)
              # search-paths: '{application}' # 可选:在仓库内按服务名分目录存放配置
              # default-label: main # 可选:指定默认分支
          # 可选:将 Config Server 注册到 Nacos
          nacos:
            discovery:
              server-addr: 127.0.0.1:8848
        # 可选:启用 Webhook 监控
        bus:
          enabled: true # 需要 spring-cloud-config-monitor 和消息总线 (如 RabbitMQ)
    
    # 可选:启用消息总线 (用于 /monitor 端点广播刷新)
    # management:
    #   endpoints:
    #     web:
    #       exposure:
    #         include: bus-refresh, health, info
    
  5. 启动 Config Server:

    cd config-server
    mvn spring-boot:run
    
    • 访问 http://localhost:8888/actuator/health 应该看到 {"status":"UP"}
    • 访问 http://localhost:8888/user-service/dev (或 http://localhost:8888/user-service-dev.yml),你应该能看到合并后的配置(application.yml + user-service.yml + user-service-dev.yml)。

步骤 3:改造 Config Client (user-service, order-service)

  1. 修改 pom.xml:user-serviceorder-service 中添加 spring-cloud-starter-config 依赖。

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    <!-- 如果 Config Server 注册到了 Nacos,Client 也需要 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    
  2. 创建 bootstrap.yml: 这是关键! bootstrap.yml 的加载优先级高于 application.yml。它用于配置连接 Config Server 所需的信息。

    • user-service/src/main/resources/bootstrap.yml:
      # bootstrap.yml (加载优先级最高)
      spring:
        application:
          name: user-service # 必须与 Git 仓库中的配置文件名 {application} 对应
        cloud:
          config:
            # 方式 1: 直接指定 Config Server 地址
            uri: http://localhost:8888
            profile: dev # 指定激活的 profile
            label: main # 指定 Git 分支
            # 方式 2: 通过服务发现 (如果 Config Server 注册到了 Nacos)
            # discovery:
            #   enabled: true
            #   service-id: config-server # Config Server 在 Nacos 中的服务名
            # profile: dev
            # label: main
          # 如果使用 Nacos Discovery
          nacos:
            discovery:
              server-addr: 127.0.0.1:8848
        # 可选:配置文件名前缀 (如果 Git 仓库中配置文件有前缀)
        # cloud:
        #   config:
        #     name: myapp # 会查找 myapp-dev.yml 等
      server:
        port: 8081 # 这个端口可能会被 Config Server 的配置覆盖
      
    • order-service/src/main/resources/bootstrap.yml:
      spring:
        application:
          name: order-service
        cloud:
          config:
            uri: http://localhost:8888
            # discovery:
            #   enabled: true
            #   service-id: config-server
            profile: dev
            label: main
          nacos:
            discovery:
              server-addr: 127.0.0.1:8848
      
    • user-service/src/main/resources/application.yml: 可以删除或保留本地配置,但会被 Config Server 的配置覆盖。通常 application.yml 只保留本地开发时的配置或非常基础的配置。
      # application.yml (可选,优先级低于 bootstrap.yml 和 Config Server)
      # logging:
      #   level:
      #     root: INFO
      # server:
      #   port: 8081 # 这个会被 user-service-dev.yml 覆盖
      
  3. 验证配置加载:

    • 启动 config-server
    • 启动 user-service
    • 查看 user-service 启动日志。你应该看到类似:
      Located property source: [BootstrapPropertySource {name='bootstrapProperties-configService:user-service,dev'}]
      ...
      Tomcat initialized with port(s): 8082 (http) # 注意端口是 8082,来自 user-service-dev.yml
      
    • 访问 user-service/actuator/env 端点,搜索 configService,可以看到来自 Config Server 的属性源。

步骤 4:实现配置动态刷新 (@RefreshScope)

  1. 在需要刷新的 Bean 上添加 @RefreshScope:
    • 修改 user-serviceUserController.java
      package com.example.userservice.controller;
      
      import com.example.userservice.entity.User;
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.cloud.context.config.annotation.RefreshScope; // 引入
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.RestController;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.core.env.Environment;
      import java.util.Map;
      
      @RestController
      @RequestMapping("/users")
      @RefreshScope // 添加此注解,使该 Controller 支持配置刷新
      public class UserController {
      
          private final Environment environment; // 注入 Environment
      
          @Value("${server.port}") // 注入端口,或从 Environment 获取
          private String port;
      
          @Autowired
          public UserController(Environment environment) {
              this.environment = environment;
          }
      
          // ... (users map 省略)
      
          @GetMapping("/{id}")
          public User getUserById(@PathVariable Long id) {
              User user = users.get(id);
              if (user != null) {
                  // 演示从 Environment 获取配置
                  String serverPort = environment.getProperty("server.port");
                  // 或使用 @Value 注入的 port
                  user.setName(user.getName() + " (Port: " + serverPort + ")");
                  // 添加一个自定义配置项用于测试刷新
                  String customInfo = environment.getProperty("app.custom.info", "Default Info");
                  user.setEmail(user.getEmail() + " | " + customInfo);
              }
              return user;
          }
      }
      
  2. 在 Git 仓库中添加可刷新的配置:
    • 编辑 user-service-dev.yml,添加:
      # user-service-dev.yml
      server:
        port: 8082
      logging:
        level:
          com.example.userservice: DEBUG
      # 添加自定义配置
      app:
        custom:
          info: "Hello from Config Server! (Dev Env)"
      
    • 提交并推送到 Git 仓库。
  3. 触发刷新:
    • 方式一:手动调用 Actuator 端点:
      • 确保 user-servicepom.xmlspring-boot-starter-actuator
      • application.yml (或 bootstrap.yml) 中暴露 refresh 端点:
        # user-service/src/main/resources/application.yml
        management:
          endpoints:
            web:
              exposure:
                include: refresh, health, info
        
      • 重启 user-service (让新配置生效)。
      • 访问 http://localhost:8082/users/1email 字段应包含 Hello from Config Server! (Dev Env)
      • 修改配置:user-service-dev.yml 中的 app.custom.info 改为 "Updated Info! (Dev Env)",提交并推送到 Git。
      • 刷新客户端:user-service 发送 POST 请求到 /actuator/refresh
        curl -X POST http://localhost:8082/actuator/refresh
        # 返回 ["app.custom.info"] 表示刷新成功
        
      • 再次访问 http://localhost:8082/users/1email 字段应显示更新后的信息。
    • 方式二:使用消息总线 (Bus) + Webhook (自动刷新):
      • 前提: config-server 和所有 config-client 都集成了消息总线(如 RabbitMQ, Kafka)和 spring-cloud-starter-bus-amqp / spring-cloud-starter-bus-kafka
      • config-server 配置 spring-cloud-config-monitor
      • 在 Git 仓库设置 Webhook,指向 config-server/monitor 端点。
      • 当 Git 仓库有 push 事件时,Webhook 触发 /monitor
      • config-server 通过消息总线向所有 config-client 广播刷新消息。
      • 所有 @RefreshScope 的 Bean 自动刷新。这是最优雅的方案。

三、常见错误

  1. InvalidConfigServerException: Could not locate PropertySource: I/O error on GET request for ...:

    • 原因: config-client 无法连接到 config-server
    • 解决: 检查 bootstrap.yml 中的 uri 是否正确,config-server 是否已启动,网络是否通畅。
  2. NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.cloud.client.discovery.DiscoveryClient' available:

    • 原因: 使用了 discovery.enabled: true,但项目中没有引入服务发现客户端(如 spring-cloud-starter-alibaba-nacos-discovery)。
    • 解决: 添加相应的发现客户端依赖。
  3. 配置未生效 / 本地配置覆盖了远程配置:

    • 原因 1: 配置文件名 (spring.application.name) 与 Git 仓库中的 {application} 不匹配。
    • 解决: 检查 bootstrap.yml 中的 spring.application.name
    • 原因 2: bootstrap.yml 不存在或配置错误。
    • 解决: 确保 bootstrap.yml 文件存在且在 src/main/resources 目录下。
    • 原因 3: config-clientapplication.yml 中有同名配置,且优先级更高(但通常 Config Server 配置优先级更高,除非 spring.cloud.config.override-none=true)。
    • 解决: 检查配置优先级规则。
  4. @RefreshScope 刷新失败:

    • 原因 1: Bean 没有添加 @RefreshScope 注解。
    • 解决: 为需要刷新的 Bean 添加注解。
    • 原因 2: management.endpoints.web.exposure.include 没有包含 refresh
    • 解决:application.yml 中正确配置 Actuator 端点暴露。
    • 原因 3: 刷新时 Bean 初始化失败(如注入的依赖有问题)。
    • 解决: 检查日志中的错误信息。
  5. Git 仓库认证失败:

    • 原因: config-serverapplication.ymlusername/password 错误,或 Token 过期。
    • 解决: 检查凭据,使用 Personal Access Token 更安全。

四、注意事项

  1. bootstrap.yml vs application.yml: bootstrap.yml 用于引导应用,加载连接 Config Server 的配置。application.yml 用于应用自身的配置。bootstrap.yml 优先级更高
  2. 配置优先级: Spring Boot 有复杂的配置优先级。Config Server 的配置通常优先级很高,但可以通过 spring.cloud.config.override-system-properties=false (默认) 等设置微调。
  3. @RefreshScope 的限制:
    • 不能用于 @Configuration 类。
    • 不能用于 @Scheduled 注解的方法。
    • 不能用于 @Async 注解的方法。
    • 使用 @RefreshScope 的 Bean 是代理,可能影响 equals()hashCode()
  4. 安全性: Git 仓库的凭据 (password) 在 config-server 的配置中是明文的。考虑使用 spring-cloud-config-server 的加密功能或使用 Vault。
  5. 性能: Config Server 是单点。虽然它本身无状态(配置在 Git),但它是所有服务启动时的依赖。确保其高可用(如多实例 + 负载均衡)。
  6. Git 仓库大小: 避免在 Git 仓库中存储大文件(如图片、二进制文件)。
  7. spring.profiles.active:bootstrap.yml 中设置 spring.profiles.active=dev 可以激活 profile,但通常在启动命令中指定更灵活 (--spring.profiles.active=prod)。

五、使用技巧

  1. 共享配置: 利用 application-{profile}.yml 实现所有服务在特定环境的共享配置(如日志级别、公共数据库连接)。
  2. 组合配置: 一个服务可以加载多个配置文件,如 application.yml, {application}.yml, {application}-{profile}.yml, nacos-discovery.yml。它们会按优先级合并。
  3. 使用 search-paths:config-serverapplication.yml 中设置 spring.cloud.config.server.git.search-paths: '{application}',可以在 Git 仓库中按服务名创建目录,便于管理。
  4. 加密配置: 使用 /{name}/{profile}/{label}/{property} 端点加密敏感信息。需要在 config-server 配置密钥。
  5. /env 端点: config-client/actuator/env 是调试配置来源的利器,可以查看所有属性源及其值。
  6. 健康检查: config-client/actuator/health 会检查与 config-server 的连接状态。

六、最佳实践

  1. 使用 bootstrap.yml: 始终将连接 Config Server 的配置放在 bootstrap.yml
  2. 环境隔离:dev, test, prod 维护独立的配置文件或 Git 分支/标签。
  3. 版本控制: 将所有配置文件提交到 Git,利用 Git 的版本、分支、合并、回滚能力。
  4. 最小化本地配置: config-clientapplication.yml 只保留绝对必要的本地配置或占位符。
  5. 谨慎使用 @RefreshScope: 只对确实需要动态刷新的 Bean 使用。避免滥用,因为它是动态代理。
  6. 实现自动刷新: 对于生产环境,强烈建议使用 消息总线 (Bus) + Webhook 实现配置的自动推送刷新,避免手动调用。
  7. 高可用 Config Server: 部署多个 config-server 实例,并通过负载均衡器(如 Nginx)对外提供服务。
  8. 安全加固: 保护 config-server/encrypt, /decrypt 端点,使用 HTTPS,妥善管理 Git 凭据。
  9. 文档化: 在 Git 仓库中维护配置说明文档。

七、性能优化

  1. 本地文件系统缓存:
    • 问题: config-server 每次请求都去 Git 仓库拉取最新代码,开销大。
    • 优化: Config Server 会在本地克隆一份 Git 仓库。首次请求会克隆,后续请求会 git pull确保 config-server 有足够的磁盘空间。可以通过 spring.cloud.config.server.git.refresh-rate (默认 0,即每次请求都检查) 控制 pull 频率,但不推荐设为正数,以免错过更新。
  2. 客户端缓存:
    • 问题: config-client 每次启动都从 config-server 获取配置。
    • 优化: config-client 本身没有内置配置缓存。但可以通过以下方式优化:
      • 减少配置文件大小: 只放必要的配置。
      • 优化网络: 确保 config-clientconfig-server 网络延迟低。
      • 使用本地备份 (不推荐): 理论上可以在 config-client 本地存一份备份,但违背了集中化原则,容易出错。
  3. config-server 高可用与负载均衡:
    • 部署多个 config-server 实例。
    • 使用 Nginx 或云负载均衡器 (SLB) 将请求分发到多个实例。
    • 客户端通过负载均衡器的地址访问。
  4. 异步初始化:
    • 问题: config-client 启动时同步获取配置,增加了启动时间。
    • 现状: Spring Cloud Config Client 的初始化是同步的,目前没有内置的异步方案。这是其局限性之一。考虑使用 Nacos/Apollo 等支持长轮询或推送的配置中心,它们能更快地获取初始配置
  5. 监控:
    • 监控 config-server 的性能指标(如请求延迟、错误率)。
    • 监控 Git 仓库的访问情况。
    • 监控消息总线的健康状况(如果使用 Bus)。

总结

Spring Cloud Config Server 通过 集中化、版本化 的方式管理微服务配置。核心是 @EnableConfigServerspring.cloud.config.server.git.uri 和客户端的 bootstrap.yml操作上,先建 Git 仓库存配置,再启 config-server,最后在 config-clientbootstrap.yml 中配置连接信息@RefreshScope + /actuator/refreshBus + Webhook 实现动态刷新注意 bootstrap.yml 的优先级和 @RefreshScope 的限制最佳实践包括环境隔离、版本控制和自动刷新性能优化主要靠 config-server 高可用和本地 Git 缓存。虽然功能强大,但同步初始化和 RefreshScope 限制是其痛点,Nacos/Apollo 等一体化平台提供了更流畅的体验