一、核心概念
在微服务架构中,随着服务数量增多,将配置(如数据库连接、开关、超时时间)硬编码在代码中或分散在各个服务器的配置文件中变得难以管理、易出错、无法动态更新。配置中心(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 仓库的分支或标签。默认是master
或main
。
查找顺序 (重要):
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{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-service
和 order-service
作为 config-client
从它获取配置。
步骤 1:准备 Git 仓库(后端存储)
- 创建一个 Git 仓库:
- 在 Gitee、GitHub 或本地创建一个新仓库,例如
spring-cloud-config-repo
。
- 在 Gitee、GitHub 或本地创建一个新仓库,例如
- 创建配置文件:
- 在仓库根目录下创建以下文件:
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
- 在仓库根目录下创建以下文件:
- 提交并推送到远程仓库:
git add . git commit -m "Initial config files" git push origin main
步骤 2:创建 Config Server (config-server
)
创建项目:
- 使用 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)
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>
主类
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); } }
配置
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
启动 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
)
修改
pom.xml
: 在user-service
和order-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>
创建
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 覆盖
验证配置加载:
- 启动
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
)
- 在需要刷新的 Bean 上添加
@RefreshScope
:- 修改
user-service
的UserController.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; } }
- 修改
- 在 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 仓库。
- 编辑
- 触发刷新:
- 方式一:手动调用 Actuator 端点:
- 确保
user-service
的pom.xml
有spring-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/1
,email
字段应包含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/1
,email
字段应显示更新后的信息。
- 确保
- 方式二:使用消息总线 (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 自动刷新。这是最优雅的方案。
- 前提:
- 方式一:手动调用 Actuator 端点:
三、常见错误
InvalidConfigServerException: Could not locate PropertySource: I/O error on GET request for ...
:- 原因:
config-client
无法连接到config-server
。 - 解决: 检查
bootstrap.yml
中的uri
是否正确,config-server
是否已启动,网络是否通畅。
- 原因:
NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.cloud.client.discovery.DiscoveryClient' available
:- 原因: 使用了
discovery.enabled: true
,但项目中没有引入服务发现客户端(如spring-cloud-starter-alibaba-nacos-discovery
)。 - 解决: 添加相应的发现客户端依赖。
- 原因: 使用了
配置未生效 / 本地配置覆盖了远程配置:
- 原因 1: 配置文件名 (
spring.application.name
) 与 Git 仓库中的{application}
不匹配。 - 解决: 检查
bootstrap.yml
中的spring.application.name
。 - 原因 2:
bootstrap.yml
不存在或配置错误。 - 解决: 确保
bootstrap.yml
文件存在且在src/main/resources
目录下。 - 原因 3:
config-client
的application.yml
中有同名配置,且优先级更高(但通常 Config Server 配置优先级更高,除非spring.cloud.config.override-none=true
)。 - 解决: 检查配置优先级规则。
- 原因 1: 配置文件名 (
@RefreshScope
刷新失败:- 原因 1: Bean 没有添加
@RefreshScope
注解。 - 解决: 为需要刷新的 Bean 添加注解。
- 原因 2:
management.endpoints.web.exposure.include
没有包含refresh
。 - 解决: 在
application.yml
中正确配置 Actuator 端点暴露。 - 原因 3: 刷新时 Bean 初始化失败(如注入的依赖有问题)。
- 解决: 检查日志中的错误信息。
- 原因 1: Bean 没有添加
Git 仓库认证失败:
- 原因:
config-server
的application.yml
中username
/password
错误,或 Token 过期。 - 解决: 检查凭据,使用 Personal Access Token 更安全。
- 原因:
四、注意事项
bootstrap.yml
vsapplication.yml
:bootstrap.yml
用于引导应用,加载连接 Config Server 的配置。application.yml
用于应用自身的配置。bootstrap.yml
优先级更高。- 配置优先级: Spring Boot 有复杂的配置优先级。Config Server 的配置通常优先级很高,但可以通过
spring.cloud.config.override-system-properties=false
(默认) 等设置微调。 @RefreshScope
的限制:- 不能用于
@Configuration
类。 - 不能用于
@Scheduled
注解的方法。 - 不能用于
@Async
注解的方法。 - 使用
@RefreshScope
的 Bean 是代理,可能影响equals()
和hashCode()
。
- 不能用于
- 安全性: Git 仓库的凭据 (
password
) 在config-server
的配置中是明文的。考虑使用spring-cloud-config-server
的加密功能或使用 Vault。 - 性能: Config Server 是单点。虽然它本身无状态(配置在 Git),但它是所有服务启动时的依赖。确保其高可用(如多实例 + 负载均衡)。
- Git 仓库大小: 避免在 Git 仓库中存储大文件(如图片、二进制文件)。
spring.profiles.active
: 在bootstrap.yml
中设置spring.profiles.active=dev
可以激活 profile,但通常在启动命令中指定更灵活 (--spring.profiles.active=prod
)。
五、使用技巧
- 共享配置: 利用
application-{profile}.yml
实现所有服务在特定环境的共享配置(如日志级别、公共数据库连接)。 - 组合配置: 一个服务可以加载多个配置文件,如
application.yml
,{application}.yml
,{application}-{profile}.yml
,nacos-discovery.yml
。它们会按优先级合并。 - 使用
search-paths
: 在config-server
的application.yml
中设置spring.cloud.config.server.git.search-paths: '{application}'
,可以在 Git 仓库中按服务名创建目录,便于管理。 - 加密配置: 使用
/{name}/{profile}/{label}/{property}
端点加密敏感信息。需要在config-server
配置密钥。 /env
端点:config-client
的/actuator/env
是调试配置来源的利器,可以查看所有属性源及其值。- 健康检查:
config-client
的/actuator/health
会检查与config-server
的连接状态。
六、最佳实践
- 使用
bootstrap.yml
: 始终将连接 Config Server 的配置放在bootstrap.yml
。 - 环境隔离: 为
dev
,test
,prod
维护独立的配置文件或 Git 分支/标签。 - 版本控制: 将所有配置文件提交到 Git,利用 Git 的版本、分支、合并、回滚能力。
- 最小化本地配置:
config-client
的application.yml
只保留绝对必要的本地配置或占位符。 - 谨慎使用
@RefreshScope
: 只对确实需要动态刷新的 Bean 使用。避免滥用,因为它是动态代理。 - 实现自动刷新: 对于生产环境,强烈建议使用 消息总线 (Bus) + Webhook 实现配置的自动推送刷新,避免手动调用。
- 高可用 Config Server: 部署多个
config-server
实例,并通过负载均衡器(如 Nginx)对外提供服务。 - 安全加固: 保护
config-server
的/encrypt
,/decrypt
端点,使用 HTTPS,妥善管理 Git 凭据。 - 文档化: 在 Git 仓库中维护配置说明文档。
七、性能优化
- 本地文件系统缓存:
- 问题:
config-server
每次请求都去 Git 仓库拉取最新代码,开销大。 - 优化: Config Server 会在本地克隆一份 Git 仓库。首次请求会克隆,后续请求会
git pull
。确保config-server
有足够的磁盘空间。可以通过spring.cloud.config.server.git.refresh-rate
(默认 0,即每次请求都检查) 控制pull
频率,但不推荐设为正数,以免错过更新。
- 问题:
- 客户端缓存:
- 问题:
config-client
每次启动都从config-server
获取配置。 - 优化:
config-client
本身没有内置配置缓存。但可以通过以下方式优化:- 减少配置文件大小: 只放必要的配置。
- 优化网络: 确保
config-client
与config-server
网络延迟低。 - 使用本地备份 (不推荐): 理论上可以在
config-client
本地存一份备份,但违背了集中化原则,容易出错。
- 问题:
config-server
高可用与负载均衡:- 部署多个
config-server
实例。 - 使用 Nginx 或云负载均衡器 (SLB) 将请求分发到多个实例。
- 客户端通过负载均衡器的地址访问。
- 部署多个
- 异步初始化:
- 问题:
config-client
启动时同步获取配置,增加了启动时间。 - 现状: Spring Cloud Config Client 的初始化是同步的,目前没有内置的异步方案。这是其局限性之一。考虑使用 Nacos/Apollo 等支持长轮询或推送的配置中心,它们能更快地获取初始配置。
- 问题:
- 监控:
- 监控
config-server
的性能指标(如请求延迟、错误率)。 - 监控 Git 仓库的访问情况。
- 监控消息总线的健康状况(如果使用 Bus)。
- 监控
总结
Spring Cloud Config Server 通过 集中化、版本化 的方式管理微服务配置。核心是 @EnableConfigServer
、spring.cloud.config.server.git.uri
和客户端的 bootstrap.yml
。操作上,先建 Git 仓库存配置,再启 config-server
,最后在 config-client
的 bootstrap.yml
中配置连接信息。@RefreshScope
+ /actuator/refresh
或 Bus + Webhook
实现动态刷新。注意 bootstrap.yml
的优先级和 @RefreshScope
的限制。最佳实践包括环境隔离、版本控制和自动刷新。性能优化主要靠 config-server
高可用和本地 Git 缓存。虽然功能强大,但同步初始化和 RefreshScope 限制是其痛点,Nacos/Apollo 等一体化平台提供了更流畅的体验。