CodeRoad Map 程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。
由 程序员柏风 独立维护 · 关于作者
学习 更多
Cloud Config 配置中心 集中配置、Git 仓库、动态刷新。
Cloud Config 配置中心
这一课你将能做到
搭起一个 Config Server(本地文件模式和 Git 模式都会配),让 problem-service 启动时自动拉取远程配置
用 @RefreshScope 实现「改配置不重启」:改一个限流开关,调 refresh 端点立刻生效
说清配置刷新的底层原理和 spring.config.import 的加载顺序
为什么要学
三个微服务时每人一份 application.yml 还行;十个服务 × dev/test/prod 三环境就是 30 份文件。改一个 Redis 地址要改 10 份再逐个重启,漏一份就是线上事故。配置中心 把配置从代码仓库剥离、集中到一处:多环境隔离、按服务命名、支持动态刷新。
Spring Cloud Config 是官方方案:配置存在 Git 仓库(天然有版本历史和审批流),Config Server 对外提供读取,各服务作为 Config Client 启动时拉取。国内 Nacos Config 也是同类方案(带 UI、推送更快),原理相通——学懂 Config,「配置如何集中、如何刷新」这套心智模型直接平移。
先建立图像
┌─────────────────────────────┐
│ Git 仓库 config-repo │
│ application.yml │ ← 所有服务共享的默认配置
│ problem-service.yml │ ← 服务专属配置
│ problem-service-dev.yml │ ← 服务 + 环境专属配置
└──────────────┬──────────────┘
│ Server 拉取(可缓存)
┌──────────────▼──────────────┐
│ Config Server :8888 │
└──────────────┬──────────────┘
│ Client 启动时 HTTP 拉取 /actuator/refresh 时重拉
┌───────────┼───────────┐
▼ ▼ ▼
user-svc problem-svc judge-svc
Client 端加载优先级(高 → 低):problem-service-dev.yml > problem-service.yml > application-dev.yml > application.yml > 本地 application.yml。远程配置默认覆盖本地同名项 。
跟着做
步骤 1:搭建 Config Server
新建模块 config-server,pom.xml:
<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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>cloud-demo</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>config-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
</project>
启动类:
package com.example.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
本地演示先用 native 模式(直接读本地目录,不依赖 Git),application.yml:
server:
port: 8888
spring:
application:
name: config-server
profiles:
active: native
cloud:
config:
server:
native:
search-locations: file:///D:/work/config-repo # Windows 路径;Linux 用 file:///opt/config-repo
步骤 2:准备配置仓库 judge:
daily-limit: 20 # 每日提交上限
enable-new-engine: false # 判题引擎灰度开关
server:
port: 8082
management:
endpoints:
web:
exposure:
include: health,refresh
curl http://localhost:8888/problem-service/dev
返回 JSON 里能看到 propertySources 按「服务+环境 → 服务 → 全局」顺序排列,后面的覆盖前面的。
spring:
profiles:
active: git
cloud:
config:
server:
git:
uri: https://github.com/your-org/config-repo
default-label: main
# 私有仓库追加:
# username: ${GIT_USER}
# password: ${GIT_TOKEN}
生产用 Git 模式:每次改配置是一次 commit,有历史、可回滚、可审计。
步骤 3:Client 接入 <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
problem-service 的 application.yml(Boot 3.x / Cloud 2023.x 用 spring.config.import,旧版 bootstrap.yml 已弃用):
spring:
application:
name: problem-service
profiles:
active: dev
config:
import: optional:configserver:http://localhost:8888
optional: 表示 Config Server 挂了也能用本地配置启动(演示友好;生产可去掉 optional 强依赖)。
Client 会按 {应用名}-{profile}.yml 的规则去 Server 拉取。
package com.example.problem.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FeatureController {
@Value("${judge.daily-limit:0}")
private int dailyLimit;
@Value("${judge.enable-new-engine:false}")
private boolean newEngine;
@GetMapping("/features")
public String features() {
return "dailyLimit=" + dailyLimit + ", newEngine=" + newEngine;
}
}
访问 http://localhost:8082/features,能看到配置中心下发的值。
步骤 4:动态刷新(不重启) package com.example.problem.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
@Component
@RefreshScope
@ConfigurationProperties(prefix = "judge")
public class JudgeProperties {
private int dailyLimit;
private boolean enableNewEngine;
public int getDailyLimit() { return dailyLimit; }
public void setDailyLimit(int dailyLimit) { this.dailyLimit = dailyLimit; }
public boolean isEnableNewEngine() { return enableNewEngine; }
public void setEnableNewEngine(boolean enableNewEngine) { this.enableNewEngine = enableNewEngine; }
}
修改 config-repo 里的 judge.daily-limit 为 50 并保存(Git 模式下是提交并推送),然后触发刷新:
curl -X POST http://localhost:8082/actuator/refresh
再访问 /features:值已变成 50,服务没有重启 。注意 actuator 需要在依赖里(spring-boot-starter-actuator),且 exposure 包含 refresh。
讲清楚要点
两个角色 :Config Server 读后端存储(Git/本地/ SVN)对外提供 HTTP 读取;Config Client 启动时拉取并合并进自己的 Environment。
命名规则决定拉什么 :{application}、{application}-{profile}、{application}/{label}(label 即 Git 分支);最常用的组合是 problem-service-dev.yml。
刷新原理(面试高频) :POST /actuator/refresh → Client 重新从 Server 拉取 Environment → 发布 EnvironmentChangeEvent → @ConfigurationProperties Bean 重新绑定 → @RefreshScope 的 Bean 被销毁,下次使用时懒加载重建 ,于是拿到新值。
@RefreshScope 是白名单机制 :没打这个注解的 Bean(比如普通 @Value 注入的单例)刷新后仍是旧值——这是最容易踩的坑。
启动期配置刷新不了 :数据源连接、端口这类在启动阶段就固定的配置,改了必须重启;适合刷新的是业务开关、阈值、白名单等运行期参数。
Spring Cloud Bus 是广播手段 :单实例手动 curl 可以,几十个实例就要用 Bus——向 MQ 发一条刷新事件,所有订阅实例各自触发 refresh,配合 Git webhook 实现「一次提交全集群生效」。
你可能会懵的地方
为什么不直接用环境变量?
十二因子应用确实推荐环境变量,但它只适合少量启动期配置(镜像里不可变);配置中心的价值在「集中 + 多环境 + 动态刷新 + 版本审计」。两者不冲突:启动项用环境变量,业务开关进配置中心。
@Value 和 @ConfigurationProperties 哪个配合刷新更好?
@ConfigurationProperties + @RefreshScope。@ConfigurationProperties 支持宽松绑定与校验,且 EnvironmentChangeEvent 本身就会触发它重新绑定;@Value 必须连带 RefreshScope 才会更新。
Config Server 挂了服务还能启动吗?
用了 optional:configserver: 前缀可以(回退本地配置);生产建议:Client 本地缓存上次拉到的配置(spring.cloud.config.fail-fast 与重试配置),Server 多实例部署在网关后面。
配置里有密码怎么办?
Config 支持对称/非对称加密:把 {cipher}密文 存进 Git,Server 启动时配 encrypt key,返回给 Client 前自动解密。也可交给 Vault/Nacos 的加密能力。密码永远不要明文进 Git。
Nacos Config 和 Config 怎么选?
Nacos:长轮询推送、秒级生效、带控制台、注册配置一体,国内新项目主流;Config:Git 审计流天然强、与官方栈零成本。选型看团队:要 UI 和推送选 Nacos,要 Git 流程选 Config,模型一致不用慌。
错法对照 错法 问题 正解 Client 写死 http://localhost:8888 进镜像 环境迁移即失效 configserver:${CONFIG_URL:...} 环境变量注入改了配置只在 Server 侧改,Client 不动 Client 只有启动/refresh 时才拉取 改完必须触发 /actuator/refresh 或走 Bus 给数据源 Bean 加 @RefreshScope 期待热更新 连接池启动期已固定,刷新无效反而生乱 启动期配置接受重启;只热更业务开关 所有配置都堆 application.yml 共享文件 服务间互相覆盖,职责不清 共享放 application.yml,服务差异放 {服务名}-{profile}.yml refresh 端点对公网开放 任何人可改运行时行为 网关不路由 actuator,或加 Spring Security 保护
卡点急救
Client 启动报 Config data resource ... does not exist 或直接失败 :去掉了 optional: 但 Server 没起;或 Server 侧 search-locations 路径写错。先用 curl http://localhost:8888/problem-service/dev 验证 Server 是否能返回内容。
refresh 后值没变 :①Bean 没加 @RefreshScope;②actuator 没暴露 refresh 端点;③native 模式下文件没保存生效。三处逐一排查,/actuator/refresh 的响应体会列出哪些 key 变了。
配置文件中文乱码 :config-repo 里的 yml 必须是 UTF-8 编码;Windows 下用记事本编辑常被存成 GBK,换 VS Code 并确认右下角编码。
面试常问
配置动态刷新的原理? (必背)
①Client 收到 POST /actuator/refresh → ②重新向 Config Server 拉取配置构造新 Environment → ③发布 EnvironmentChangeEvent,对比前后差异 → ④所有 @ConfigurationProperties Bean 重新绑定 → ⑤@RefreshScope 标注的 Bean 从容器销毁,下次注入时用新 Environment 懒创建 → ⑥普通单例 Bean 不受影响(保持旧值)。追问 Bus 就补「通过 MQ 广播 RefreshRemoteApplicationEvent,避免逐台 curl」。
多个服务共享配置怎么设计?
三层:application.yml 放全局默认(日志格式、actuator 暴露)→ {service}.yml 放服务专属 → {service}-{profile}.yml 放环境差异。优先级由高到低加载,同名键高层覆盖低层。能讲出「为什么密码不放共享层」更好(最小可见性)。
bootstrap.yml 和 spring.config.import 的区别?
Cloud 2020 前用 bootstrap.yml 在「父上下文」先加载 Config Server 地址再拉配置;2020+ 改为标准 spring.config.import=configserver: 机制,把远程配置纳入 Spring Boot 统一的配置导入流程。新项目一律用 import,见到 bootstrap 意识到是老项目即可。
自测三问
Client 端从配置中心拉取的优先级顺序是什么?
/actuator/refresh 触发后容器里发生了哪几件事?
为什么 @RefreshScope 之外的 Bean 刷新后还是旧值?
小结一句话 配置中心把配置从代码剥离集中到 Git:Server 统一供读、Client 启动拉取、@RefreshScope 配合 refresh 端点实现不重启的热更新。
延伸