通过 Nacos 实现参数配置(模板方法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| . ├── core # 动态线程池核心模块包,实现动态线程池相关基础类定义 ├── dashboard-dev # 前端页面方便查看和调试 ├── example # 动态线程池示例包,演示线程池动态参数变更、监控和告警等功能 │ ├── apollo-example │ └── nacos-cloud-example ├── spring-base # 动态线程池基础模块包,包含Spring扫描动态线程池、是否启用以及Banner打印等 └── starter # 动态线程池配置中心组件包,实现线程池结合Spring框架和配置中心动态刷新 ├── adapter # 动态线程池适配层,比如对接 Web 容器 Tomcat 线程池等 │ └── web-spring-boot-starter # Web 容器线程池组件库 ├── apollo-spring-boot-starter # Apollo 配置中心动态监控线程池组件库 ├── common-spring-boot-starter # 配置中心公共监听等逻辑抽象组件库 ├── dashboard-dev-spring-boot-starter # 控制台 API 组件库 └── nacos-cloud-spring-boot-starter # Nacos 配置中心动态监控线程池组件库
|
什么是 Nacos?
Nacos是 Dynamic Naming and Configuration Service 的首字母简称,一个更易于构建云原生应用的 动态服务发现、配置管理和服务管理平台
Nacos 如何完成配置监听?
1. 注册 Nacos Listener
从 Nacos 提供的配置中心抽象接口中,我们可以看到其核心方法之一是 addListener,用于注册配置变更监听器。
1 2 3
| 向配置添加【监听器】,当【服务端】修改配置后,【客户端】会通过传入的【监听器】进行回调。推荐使用异步处理,应用可以在 ManagerListener 中实现 getExecutor 方法,提供一个用于执行的线程池。如果没有提供,将使用主线程进行回调,这可能会阻塞其他配置,或被其他配置阻塞 void addListener(String dataId, String group, Listener listener) throws NacosException;
|
什么时机进行添加监听呢?
这里使用 SpringBoot 扩展接口 ApplicationRunner 实现,一个启动时自动回调的“钩子”接口
方法的具体逻辑可以分为以下几个步骤:
- 获取
Nacos配置参数 :首先,通过 properties.getNacos() 获取配置中心的关键参数,比如 dataId 和 group,这两个字段共同确定了唯一的配置文件位置。
- 注册配置变更监听器 :接着调用
configService.addListener(...) 方法,向指定的 dataId 和 group 注册监听器。一旦 Nacos 端对应的配置发生变更,监听器就会被自动触发。
- 自定义
Listener的执行线程池 :在匿名 Listener 实现中,重写了 getExecutor() 方法,自定义了一个单线程池 来异步执行回调逻辑,避免阻塞 Nacos 客户端的主线程,同时也规避了并发带来的副作用。
- 配置变更回调 :当配置发生变更时,
receiveConfigInfo(...) 方法会被回调。我们通常会在这里调用 refreshThreadPoolProperties(...),将最新的配置信息解析出来并动态刷新线程池参数。
- 日志输出 :为了方便后续运维排查,注册成功后会打印一条
info 级别的日志,明确表示监听器已经生效。
registerListener()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
@Override protected void registerListener() throws Exception { BootstrapConfigProperties.NacosConfig nacosConfig = properties.getNacos(); configService.addListener( nacosConfig.getDataId(), nacosConfig.getGroup(), new Listener() {
@Override public Executor getExecutor() { return ThreadPoolExecutorBuilder.builder() .corePoolSize(1) .maximumPoolSize(1) .keepAliveTime(9999L) .workQueueType(BlockingQueueTypeEnum.SYNCHRONOUS_QUEUE) .threadFactory("clod-nacos-refresher-thread_") .rejectedHandler(new ThreadPoolExecutor.CallerRunsPolicy()) .build(); }
@Override public void receiveConfigInfo(String configInfo) { refreshThreadPoolProperties(configInfo); } }); log.info("..."); }
|
2. 配置属性动态绑定
Nacos 在配置变更时传递给监听器的,是整个配置文件内容的字符串。由于我们配置的是 YAML格式 ,所以这里接收到的也是一整段 YAML 字符串。但这段字符串是原始内容,没法直接用在业务逻辑里。那我们该怎么办?很简单:将它解析为Java对象。在我们的项目中,就是将它转换为 BootstrapConfigProperties 实例,后续线程池的动态刷新才可以进行。
1 2 3 4 5 6 7 8 9 10 11 12
| Map<Object, Object> configInfoMap = ConfigParserHandler.getInstance().parseConfig(configInfo, properties.getConfigFileType());
ConfigurationPropertySource sources = new MapConfigurationPropertySource(configInfoMap); Binder binder = new Binder(sources);
BootstrapConfigProperties refresherProperties = binder.bind( BootstrapConfigProperties.PREFIX, Bindable.ofInstance(properties) ).get();
|
2.1 configInfo → Map:配置字符串解析
将来自 Nacos 的配置内容(字符串)解析为一个扁平化的 Map<Object, Object>,用于后续绑定
2.2 Map → PropertySource:适配为 Spring 配置源
将 Map 封装为 SpringBoot 的 ConfigurationPropertySource,让其具备“配置绑定”的能力。该能力为 SpringBoot 原生提供的能力。同时,创建一个配置绑定器,用于将 PropertySource 中的配置项绑定到 Java 对象上。Binder 是 SpringBoot 的底层绑定引擎,能够将配置源的数据绑定到 Java 对象中
2.3 bind → Java对象:绑定为配置类实例
通过 Binder 将配置源的内容绑定到已有的 properties 对象上,生成一个最新的配置实例 refresherProperties。其中的 "ysythread" 是绑定的前缀,表示只会注入这个前缀下对应的配置字段
3. Starter 自动装配
和我们之前构建 common-spring-boot-starter 的过程一样
使用模板方法重构刷新事件
支持 Nacos 和 Apollo 两种主流配置中心,后期也方便扩展其他的配置中心方法,符合 开闭原则(对扩展开放,对修改关闭)
定义抽象类 AbstractDynamicThreadPoolRefresher
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Slf4j @RequiredArgsConstructor public abstract class AbstractDynamicThreadPoolRefresher implements ApplicationRunner { protected final BootstrapConfigProperties properties;
protected abstract void registerListener() throws Exception;
protected void beforeRegister() { }
protected void afterRegister() { } @Override public void run(ApplicationArguments args) throws Exception { beforeRegister(); registerListener(); afterRegister(); } }
|
其中 registerListener()之前定义了,nacos刷新的方法继承了此抽象类
implements ApplicationRunner也交给抽象类来做,此抽象类还提供了beforeRegister()、afterRegister()的方法供子类按需重写