配置中心折腾手记
考虑到我们的技术栈是 Spring Cloud,团队对阿里的技术比较熟悉,部署简单也是一个重要因素,最后选了 Nacos 2.2.3 版本。
为什么需要配置中心
在微服务架构里,配置分散在各个应用中会有几个明显问题:
改配置就要重部署。这是最直接的痛点。有些配置项本来就很频繁地调整,比如限流阈值、灰度比例、功能开关,每次改都走完整发布流程,运维压力大。
环境差异管理麻烦。开发、测试、生产环境配置不同,以前靠不同 profile 文件区分,但容易出错。有一次生产环境用了开发环境的数据库地址,还好测试时发现了。
配置没有版本控制。改了什么、什么时候改的、谁改的,这些都追溯不到。回滚更是凭记忆。
权限管控不足。谁都能改配置文件,但不是所有人都应该有权限。敏感信息比如数据库密码、API 密钥,直接写在配置文件里本身就是风险。
技术选型:Nacos 还是 Apollo
当时市面上主流的配置中心有 Nacos、Apollo、Consul。我从这几个维度对比了一下:
Nacos 是阿里开源的,Spring Cloud Alibaba 原生集成,配置管理和服务注册发现二合一。优点是部署简单、文档齐全、社区活跃。缺点是历史包袱稍重,老版本有一些已知 bug。
Apollo 是携程开源的,专注配置管理,灰度发布和权限控制做得很好。优点是功能全面、权限管理细粒度、支持多环境配置。缺点是部署相对复杂,需要多个组件。
Consul 是 HashiCorp 的,服务发现和配置管理都有。优点是轻量级、性能好。缺点是配置管理功能相对基础,没有专门的 UI 管理界面。
考虑到我们的技术栈是 Spring Cloud,团队对阿里的技术比较熟悉,部署简单也是一个重要因素,最后选了 Nacos 2.2.3 版本。
Nacos 部署和集成
Nacos 部署有单机模式和集群模式,我们先用单机模式跑起来,后续再考虑集群。
# 下载 Nacos
wget https://github.com/alibaba/nacos/releases/download/2.2.3/nacos-server-2.2.3.tar.gz
# 解压
tar -xvf nacos-server-2.2.3.tar.gz
# 启动单机模式
cd nacos/bin
./startup.sh -m standalone
启动成功后访问 http://localhost:8848/nacos,默认账号密码都是 nacos。
接下来在 Spring Boot 应用中集成 Nacos 配置中心:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
在 bootstrap.yml 中配置 Nacos:
spring:
application:
name: user-service
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
namespace: dev
group: DEFAULT_GROUP
file-extension: yaml
shared-configs:
- data-id: common-config.yaml
group: DEFAULT_GROUP
refresh: true
在 Nacos 控制台创建配置,Data ID 为 user-service.yaml,Group 为 DEFAULT_GROUP,配置内容可以直接把原来的 application.yml 迁移过来。
配置热更新的实现
Nacos 配置更新后,默认会自动刷新到应用中。但要注意,不是所有字段都能热更新。
可以热更新的配置:Spring 的 @ConfigurationProperties 注解的 Bean、使用 @Value 注解的字段(但需要配合 @RefreshScope)、自定义的配置类。
不能热更新的配置:数据库连接池、线程池大小、端口等启动时初始化的配置。
这里有个坑:我们之前有个限流配置用 @Value 注解,改了配置后发现没生效,加上 @RefreshScope 才正常:
@RefreshScope
@RestController
public class RateLimitController {
@Value("${rate.limit:100}")
private int rateLimit;
@GetMapping("/limit")
public int getRateLimit() {
return rateLimit;
}
}
但 @RefreshScope 也有副作用:它会创建代理类,增加一点开销,而且依赖注入可能会有一些意想不到的行为。后来我们改成了 @ConfigurationProperties:
@Configuration
@ConfigurationProperties(prefix = "app")
@RefreshScope
public class AppConfig {
private int rateLimit = 100;
private boolean featureSwitch = false;
// getters and setters
}
这样配置更新就正常了,而且代码结构也更清晰。
踩坑记录:那些让人头疼的问题
坑一:配置更新延迟
第一次配置热更新测试时,发现改了配置后,应用要过几十秒才生效。查了很久才发现是 Nacos 客户端有个轮询间隔,默认是 30 秒。
可以在 bootstrap.yml 中调整:
spring:
cloud:
nacos:
config:
refresh-enabled: true
refresh-delay: 1000 # 改为 1 秒
但要注意:轮询间隔太短会增加 Nacos 服务器的压力。我们最后设为 5 秒,平衡了实时性和性能。
坑二:敏感信息泄露
刚开始我们把数据库密码直接写在 Nacos 配置里,后来觉得不对劲。Nacos 虽然有权限控制,但配置文件本身就是明文存储的。
解决方案是用 Nacos 的加密功能,或者更简单的方法:敏感信息放在环境变量里,配置里只放引用:
spring:
datasource:
password: ${DB_PASSWORD}
然后在应用的启动环境里设置 DB_PASSWORD 环境变量。
坑三:多环境配置混乱
最初我们用 namespace 区分环境,但后来发现有些配置是跨环境共享的,维护起来很麻烦。
改成这样:namespace 仍然区分环境,但通过 shared-configs 共享通用配置:
spring:
cloud:
nacos:
config:
namespace: ${ENV:dev}
shared-configs:
- data-id: common-config.yaml
group: COMMON_GROUP
refresh: true
- data-id: datasource-config.yaml
group: COMMON_GROUP
refresh: false
这样环境相关的配置放在各自的 namespace 里,通用配置放在 COMMON_GROUP,管理起来清晰很多。
坑四:集群部署的配置同步
单机模式跑了一段时间后,我们上了 Nacos 集群。集群配置没问题,但应用端的配置有些诡异:有时这个实例的配置更新了,另一个实例还是旧的。
查了 Nacos 文档才知道,集群模式下每个 Nacos 节点都有配置缓存,集群间数据同步有短暂延迟。应用端连接到不同的 Nacos 节点,拿到的配置可能不一致。
解决办法是配置多个 Nacos 服务器地址:
spring:
cloud:
nacos:
config:
server-addr: nacos1:8848,nacos2:8848,nacos3:8848
Nacos 客户端会自动选择一个节点连接,如果这个节点挂了,会自动切换到其他节点。
配置版本管理和回滚
Nacos 自带配置版本管理,每次配置更新都会生成一个新版本,可以在控制台查看历史版本并回滚。
但这个功能我们在生产环境基本没用过。原因是:配置回滚是立即生效的,一旦回错,影响范围不可控。
我们的做法是:重要的配置变更要走变更流程,在测试环境先验证,记录变更原因和时间。真的需要回滚时,手动改回原配置值,而不是点那个"回滚"按钮。
灰度发布 Nacos 支持得很好,可以指定部分实例使用新配置。我们通常会先发布到一个实例,观察 10 分钟没问题了再全量发布。
改造后的效果
两周的配置中心改造完成后,效果是明显的:
配置更新无需重新部署。大部分配置变更现在直接在 Nacos 控制台操作,几秒钟就能生效。那个超时时间调整的问题,现在 2 分钟就能解决。
配置管理成本降低。之前每个服务都要维护自己的配置文件,现在集中在 Nacos 管理,环境差异和通用配置都有明确的组织结构。
权限管控更严格。只有运维和特定开发人员才有权限修改生产环境配置,敏感信息也不会出现在代码仓库里。
问题追溯更清晰。每次配置变更都有时间戳和操作人记录,出问题时能快速定位。
当然也有新的问题:Nacos 成了单点依赖。虽然集群部署提高了可用性,但如果 Nacos 整个集群挂了,应用启动时拿不到配置也会受影响。
这个我们加了本地文件缓存兜底:应用启动时会从 Nacos 拉取配置,同时保存到本地文件。下次启动如果 Nacos 不可用,就用本地缓存的配置。
一点经验和反思
配置中心不是银弹,但它确实解决了很多实际问题。如果你也在考虑引入配置中心,我有几个建议:
从小规模开始。不要一次性把所有服务都迁过来,先选 1-2 个非核心服务试点,踩完坑再全量推广。
明确配置分类。哪些配置需要热更新,哪些配置只能在启动时生效,要分清楚。不能热更新的配置强行热更新,只会带来混乱。
重视灰度和回滚。生产环境配置变更一定要有灰度机制,宁可慢一点,也不要因为配置问题导致大面积故障。
保持简单。配置中心的功能很多,但不要什么都往里塞。我们之前有同事想把定时任务配置也放进去,后来发现还是代码里维护更合适。
这次配置中心改造让我明白:技术选型没有绝对的好坏,只有适不适合。Nacos 可能不是功能最强大的配置中心,但它适合我们的团队和技术栈,这就是最重要的。
配置管理看似是小事,但做好了能大幅提升开发效率和系统稳定性。就像那句老话说的:工欲善其事,必先利其器。
本文相关的代码示例和配置文件已整理到 thinkmoon/config-center-demo。
可用性说明:本文发布于 2021 年 5 月,距今已超过五年。文中涉及的软件版本、接口、下载地址、命令参数和操作界面可能已经发生变化,部分方案在当前环境下可能失效。请结合官方最新文档核对后再操作,生产环境使用前务必先行验证。
版权声明: 本文首发于 指尖魔法屋-配置中心折腾手记(https://blog.thinkmoon.cn/post/116-configuration-center-dynamic-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。