找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2069

积分

0

好友

273

主题
发表于 3 天前 | 查看: 12| 回复: 0

配置中心实践:从核心原理到SpringCloud整合思路导图

配置中心的概述

随着单体架构系统向微服务架构转型,服务实例数量激增,配置管理变得复杂。传统的本地配置文件方式难以满足动态变更和统一管理的需求,正是在这样的背景下,配置中心应运而生。本质上,它是一个用于集中管理和分发各类服务配置的Web应用程序。

配置中心的核心功能

一个配置中心的核心功能主要围绕两点展开:

  • 配置的存取:这是基础功能。配置中心必须能够持久化存储配置信息(无论是存于磁盘文件、数据库还是其他介质),并提供相应的配置查询接口。
  • 配置变更的通知:这是关键功能。当配置中心的配置发生变动时,所有依赖该配置的客户端需要及时感知,并能够执行相应的更新操作,以实现配置的动态生效。

手撸一个简易的配置中心

基于上述核心功能分析,我们来动手实现一个简易的配置中心,它同样包含服务端和客户端两部分。

一、文件工程整体分析

配置中心项目结构:客户端与服务端分离

项目工程整体分为两部分:

  • 服务端:一个独立部署的Web应用,默认端口8888,提供对配置进行增删改查的HTTP接口。
  • 客户端 (SDK):一个需要被业务系统引用的依赖包,封装了与服务端交互的所有逻辑。

二、服务端实现详解

1、配置文件的数据存储模型 ConfigFile

在配置中心存储配置时,我们定义了以下数据模型:

public class ConfigFile {

    private String fileId;

    private String name;

    private String extension;

    private String content;

    private Long lastUpdateTimestamp;

}
  • fileId:文件的全局唯一ID,由服务端在新增配置时自动生成。
  • name:文件名,旨在见名知意。
  • extension:文件后缀名,标识配置类型,如 propertiesyml 等。
  • content:配置文件的具体内容,格式需与后缀名匹配。
  • lastUpdateTimestamp:文件最后更新时间戳。用于客户端判断配置是否发生了变更。

2、文件存储层 ConfigFileStorage

为了抽象存储方式,我们定义了 ConfigFileStorage 接口:

public interface ConfigFileStorage {

    void save(ConfigFile configFile);

    void update(ConfigFile configFile);

    void delete(String fileId);

    ConfigFile selectByFileId(String fileId);

    List<ConfigFile> selectAll();

}

该接口提供了对配置的CRUD操作。目前我们已经实现了基于内存 (InMemoryConfigFileStorage) 和基于磁盘文件 (FileSystemConfigFileStorage) 的两种存储方式。

文件存储层实现类

可以在项目启动时,通过配置文件指定使用哪种存储方式,默认为磁盘文件存储。

@Configuration
public class ConfigFileStorageConfiguration {
    @Resource
    private ConfigServerProperties configServerProperties;

    @Bean
    @ConditionalOnMissingBean
    public ConfigFileStorage configFileStorage() {
        String storeType = configServerProperties.getStoreType().toLowerCase();
        if ("file".equals(storeType)) {
            return new FileSystemConfigFileStorage();
        }
        if ("memory".equals(storeType)) {
            return new InMemoryConfigFileStorage();
        }
        throw new RuntimeException("storeType=" + storeType + "无对应的ConfigFileStorage实现");
    }
}

当然,如果你想将配置存储到数据库,只需新增一个实现该接口的类即可。

3、ConfigController

ConfigController 提供了对配置文件的CRUD HTTP接口。

ConfigController提供配置CRUD接口

它内部通过调用 ConfigManager 来完成具体的业务逻辑。

4、ConfigManager

ConfigManager服务层实现

ConfigManager 作为服务层,主要负责参数封装,并调用 ConfigFileStorage 存储层实现来完成配置的持久化操作。

至此,配置中心配置存取的核心功能已经实现。服务端的逻辑相对简单,与我们日常开发的业务CRUD系统类似,只是将数据库存储替换为了文件存储。

关于配置变更通知的功能,我们选择在客户端实现。

三、客户端的实现

客户端工程结构如下:

配置中心客户端工程结构

1、ConfigFileChangedListener

配置变更监听器接口
ConfigFileChangedListener 是一个监听器接口。当客户端监听的配置内容发生变化时,客户端会回调此接口,并传入最新的配置信息。

2、ConfigService

ConfigService客户端核心类
ConfigService 封装了客户端的核心功能,允许添加对特定配置文件的监听器,以及获取配置内容。

使用示例:

// 创建一个ConfigService,传入配置中心服务端的地址
ConfigService configService = new ConfigService("localhost:8888");

// 从服务端获取配置文件的内容,文件的id是新增配置文件时自动生成的
ConfigFile config = configService.getConfig("69af6110-31e4-4cb4-8c03-8687cf012b77");

// 对某个配置文件进行监听
configService.addListener("69af6110-31e4-4cb4-8c03-8687cf012b77", new ConfigFileChangedListener() {
    @Override
    public void onFileChanged(ConfigFile configFile) {
        System.out.printf("fileId=%s配置文件有变动,最新内容为:%s%n", configFile.getFileId(), configFile.getContent());
    }
});

配置变更通知的实现原理
客户端感知配置变更主要有两种方式:

  1. Push (推送)模式:配置变更时,服务端主动将变动推送给客户端。优点是实时性高;缺点是实现复杂,服务端需维护客户端信息,客户端需提供接收接口。
  2. Pull (拉取)模式:客户端定时主动从服务端拉取配置,并判断是否有变更。优点是实现简单,服务端无状态;缺点是实时性依赖轮询间隔,间隔短则无效请求多,间隔长则感知延迟。

本项目选择了第二种 Pull 模式,主要是出于实现简便的考虑。

客户端定时拉取判断配置变更的实现

核心逻辑是:客户端启动一个定时任务(例如每5秒),轮询检查所监听的配置文件。通过比较本地缓存的lastUpdateTimestamp与服务端返回的值,来判断配置是否更新,若更新则回调对应的监听器。

至此,一个简易配置中心的服务端和客户端就完成了。我们可以用下面这张图来总结其核心工作原理:

简易配置中心核心原理图

接下来,我们将把这个自研的配置中心整合到 Spring Cloud 生态中。

Spring Cloud 配置中心的原理

在开始整合之前,有必要先了解 Spring Cloud 配置中心的工作原理。

1、项目启动时如何从配置中心加载数据?

在 Spring Cloud 环境下,项目启动之初,会先于主 Spring Boot 应用容器创建一个 Bootstrap 上下文(容器)。这个容器至关重要,专门用于与配置中心交互,拉取远程配置。

它主要做两件事:

  1. 加载 bootstrap.(yml/properties) 配置文件。这就是为什么配置中心的连接信息必须写在 bootstrap 配置文件中的原因。
  2. 加载所有 spring.factories 文件中 key 为 org.springframework.cloud.bootstrap.BootstrapConfiguration 所对应的配置类,并将其注入到这个 Bootstrap 容器中。注意,此时不会加载 @EnableAutoConfiguration 的自动配置类。

完成上述步骤后,Spring Cloud 会从这个容器中获取所有 PropertySourceLocator 接口的实现类,并依次调用其 locate 方法。

PropertySourceLocator接口定义

阅读其源码注释:

Strategy for locating (possibly remote) property sources for the Environment. Implementations should not fail unless they intend to prevent the application from starting.

翻译过来是:为 Environment 定位(可能是远程的)属性源的策略。除非实现者想阻止应用启动,否则不应失败。

关键点在于 “可能是远程的 (possibly remote)”。这几乎明示了该接口就是用于从像配置中心这样的远程源获取配置的。因此,任何配置中心只要实现 PropertySourceLocator 接口,就能接入 Spring Cloud,在应用启动时将远程配置加载到 Environment 中。

2、如何实现注入到 Bean 中的属性动态刷新?

以以下服务为例:

@RefreshScope
@Service
public class UserService {

    @Value("${sanyou.username}")
    private String username;

    public String getUsername() {
        return username;
    }
}

当类上标注了 @RefreshScope 注解后,配置中心中 sanyou.username 的值一旦发生变化,注入到 username 字段的值也会随之更新。

这是如何实现的呢?

Spring Cloud 规定,当配置中心客户端感知到服务端配置变化时,必须发布一个 RefreshEvent 事件

配置刷新流程触发

RefreshEventListener 会监听此事件。一旦收到事件,它就会执行两步操作来完成属性刷新:

RefreshEventListener监听刷新事件

  1. 再次拉取配置:核心逻辑与启动时类似,会(部分地)重建上下文,并通过 PropertySourceLocator 从配置中心拉取最新的配置属性。
  2. 刷新 Bean 属性:此处的实现非常巧妙,并非简单地将新值重新注入对象,而是通过 动态代理 机制完成的。

对于加了 @RefreshScope 注解的 Bean,Spring 在创建它时会进行特殊处理:

  • 首先,创建一个目标对象(如 UserService),并注入从配置中心拉取的属性值。
  • 然后,因为这个类有 @RefreshScope,Spring 会为这个目标对象生成一个代理对象

最终,暴露给其他组件注入和使用的,实际上是这个代理对象。

@RefreshScope Bean的动态代理结构

当配置刷新时,Spring 会销毁旧的目标对象,并创建一个新的 UserService 目标对象,同时注入最新的属性值。而那个暴露在外的代理对象始终保持不变。

当再次通过代理对象调用 getUsername() 方法时,代理会找到最新创建的目标对象,从而获取到最新的属性值。

配置刷新后代理对象指向新的目标对象

因此,对于使用者而言,就好像 UserService 的属性自动刷新了,其本质是代理对象背后所引用的真实对象被替换了。

3、源码执行流程图

为了更清晰地展示整体流程,这里提供两张核心的源码执行流程图,感兴趣的朋友可以结合源码深入研究。

3.1 启动时加载配置流程
Spring Cloud启动时从配置中心加载配置的源码流程图
最终,从配置中心获取到的所有属性都会存入应用启动时的 Environment 对象中。

3.2 配置刷新源码流程
Spring Cloud配置刷新核心源码流程图
此图额外说明了使用 @ConfigurationProperties 进行数据绑定的对象的刷新原理。

整合 Spring Cloud 和测试

了解了原理,整合工作就有了清晰的思路。

一、整合 Spring Cloud

1、ConfigCenterProperties

用于承载配置中心的连接信息。
配置中心连接属性类
需要在 bootstrap 配置文件中指定服务端地址 (serverAddr) 和要使用的配置文件ID (fileId)。

2、ConfigCenterPropertySourceLocator

这是整合的关键。我们必须实现 PropertySourceLocator 接口,以便 Spring Cloud 能通过它从我们的配置中心拉取配置。
自定义PropertySourceLocator实现
其核心逻辑是根据配置的 fileId,调用我们之前写好的客户端 ConfigService 从配置中心拉取配置,并使用 Spring 标准的 PropertySourceLoader 解析配置内容。

3、ConfigContextRefresher

它的作用是在应用启动后,向我们的配置中心客户端注册监听器。当监听到配置变更时,发布 RefreshEvent 事件来触发 Spring Cloud 的配置刷新流程。
配置刷新事件触发器

4、两个配置类

我们需要两个配置类来将上述组件声明为 Spring Bean。

4.1 ConfigCenterBootstrapConfiguration
Bootstrap阶段配置类
这个配置类负责提供 ConfigCenterPropertySourceLocatorConfigCenterPropertiesConfigService。它必须在 Bootstrap 阶段就被加载。

4.2 ConfigCenterAutoConfiguration
自动装配配置类
这个配置类负责提供 ConfigContextRefresher 等组件,可以通过标准的自动装配机制加载。

最后,需要在 spring.factories 文件中注册这两个配置类。
spring.factories配置
特别注意:由于 ConfigCenterBootstrapConfiguration 提供了关键的 PropertySourceLocator,它必须被 Bootstrap 容器加载,因此其对应的 key 是 org.springframework.cloud.bootstrap.BootstrapConfiguration

二、测试

1、新增一个配置文件

启动配置中心服务端,通过 API 工具(如 Postman)新增一个配置文件。
通过API创建配置文件
这里创建了一个 properties 类型的配置,内容为 sanyou.username=sanyou。创建成功后,服务端返回了此文件的唯一ID:79765c73-c1ef-4ea2-ba77-5d27a64c4685

2、测试 Spring Cloud 客户端

为了演示方便,测试代码与客户端代码位于同一模块。实际使用时,整合 Spring Cloud 的代码应打包成独立的依赖。

bootstrap.yml 中配置我们的配置中心:
bootstrap.yml配置示例

  • server-addr: 配置中心服务端地址 localhost:8888
  • file-id: 上一步创建的配置文件ID

编写一个简单的测试 Controller:
测试Controller

启动客户端应用,调用接口。通过调试可以看到,注入的确实是一个 UserService 的 CGLIB 代理对象。
调试视图显示UserService代理对象
此时,接口返回值为 sanyou
首次请求返回sanyou

3、测试动态刷新

现在,通过 API 将配置中心里 sanyou.username 的值修改为 sanyou333
通过API更新配置文件内容

等待约5秒(客户端的轮询间隔),控制台打印出日志 Refresh keys changed: [sanyou.username],表明属性已变更。
控制台打印配置刷新日志

再次调试查看,UserService 的代理对象没变,但其指向的目标对象已经变成了一个新的实例。
配置刷新后目标对象被替换

此时调用接口,返回值已变为 sanyou333
再次请求返回更新后的值sanyou333

测试成功!我们自研的简易配置中心已经完美整合到 Spring Cloud,并实现了配置的动态刷新功能。这个实践不仅帮助理解了配置中心的工作原理,也加深了对 Spring Cloud 配置刷新机制的认识。如果你想进一步研究或基于此进行扩展,完整代码已托管在 GitHub 上,欢迎在云栈社区交流探讨。

不足和改进

虽然我们的配置中心具备了基本功能,但作为一个生产级组件,还有许多需要完善的地方:

1、配置变更推送问题

当前使用定时拉取(Pull)模式,存在感知延迟(最多5秒)和无效请求多的问题。可改进为服务端推送(Push)长轮询(Long-Polling) 模式,以提升实时性和减少网络开销。

2、高可用问题

当前服务端是单点,存在单点故障风险。需要支持集群部署,实现高可用性。可以参考 Nacos 等成熟方案,支持 AP 或 CP 模式。

3、通信协议和序列化协议

目前使用 HTTP + JSON,优点是简单通用。但为了追求更高性能,可以考虑改用 gRPC 等协议,以及 Hessian、Protobuf 等更高效的序列化方式。

4、多租户隔离

当前仅通过 fileId 区分配置,隔离能力弱。应引入命名空间(Namespace)分组(Group) 等概念,实现不同应用、不同环境(如dev, test, prod)的配置隔离。

5、鉴权

缺乏安全控制。需要增加身份认证和权限管理功能,确保配置的访问和修改安全。

6、控制页面

目前只能通过 API 操作,不便于管理和查看。一个友好的Web管理控制台是提升易用性的关键。





上一篇:SpringCloud配置中心核心原理:启动加载、动态刷新与开源整合机制剖析
下一篇:Spring Cloud Alibaba选型指南:微服务核心组件详解与实战配置
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-10 13:59 , Processed in 0.427232 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表