动态线程池oneThread系统 — 第十七部分 Banner自定义和开发Nacos控制台

eve2333 发布于 4 小时前 4 次阅读


Banner就是控制台打印的东西,比如说springboot

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.7)

比如说claude code,可惜现在变成这样了


 * ▐▛███▜▌ *   Claude Code v2.0.76
* ▝▜█████▛▘ *  Sonnet 4.5 · API Usage Billing
 *  ▘▘ ▝▝  *   ~\Desktop\

oneThread 的 Banner 组件采用了模块化设计,通过自动配置机制实现无侵入集成:

Text to ASCII Art Generator (TAAG)实现banner实现

  • 入口点:CommonAutoConfiguration(通用自动配置类)。这是 Spring Boot 自动装配的机制,负责将 Banner 组件注册到 Spring 容器中。
  • 核心类:OneThreadBannerHandler。
    • 它实现了 InitializingBean 接口。这意味着当 Bean 的属性设置完成后,Spring 会自动调用 afterPropertiesSet() 方法。Banner 的打印逻辑就写在这个方法里,确保在应用启动早期执行。
    • 它依赖 BuildProperties。这是一个 Spring Boot 提供的类,用于读取构建信息(如版本号)。

新建 OneThreadBannerHandler.java 类。

package com.nageoffer.onethread.spring.config;

import cn.hutool.core.util.StrUtil;
import org.springframework.boot.ansi.AnsiColor;
import org.springframework.boot.ansi.AnsiOutput;
import org.springframework.boot.ansi.AnsiStyle;
import org.springframework.boot.info.BuildProperties;
import org.springframework.beans.factory.InitializingBean;

public class OneThreadBannerHandler implements InitializingBean {
    private static final String ONE_THREAD_DASHBOARD = "Dashboard"; // 示例
    private static final String ONE_THREAD_SITE = "Site"; // 示例
    private static final String DYNAMIC_THREAD_POOL = "Dynamic Thread Pool";
    private static final int STRAP_LINE_SIZE = 50; // 根据实际Banner宽度调整

    private final String version;

    public OneThreadBannerHandler(BuildProperties buildProperties) {
        this.version = buildProperties != null ? buildProperties.getVersion() : "";
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        String banner = """
                           ░██░██           ░██                                                       ░██           ░██
                                                                                                                      \s
                 ░███████  ░██░██ ░██████   ░██ ░███████   ░███████   ░███████   ░███████   ░███████  ░██ ░██████   ░██
                ░██    ░██ ░██░██      ░██  ░██░██    ░██ ░██    ░██ ░██    ░██ ░██    ░██ ░██    ░██ ░██      ░██  ░██
                ░██    ░██ ░██░██ ░███████  ░██░██    ░██ ░██    ░██ ░██    ░██ ░██    ░██ ░██    ░██ ░██ ░███████  ░██
                ░██    ░██ ░██░██░██   ░██  ░██░██    ░██ ░██    ░██ ░██    ░██ ░██    ░██ ░██    ░██ ░██░██   ░██  ░██
                 ░███████  ░██░██ ░█████░██ ░██ ░███████   ░███████   ░███████   ░███████   ░███████  ░██ ░█████░██ ░██
                                                                                                                      \s
                                                                                                                      \s
                                                                                                                      \s
        """; // 此处填入文档中的 ASCII 字符

        String bannerVersion = StrUtil.isNotEmpty(version) ? " (v" + version + ")" : "no version.";

        // 计算 Padding
        StringBuilder padding = new StringBuilder();
        while (padding.length() < STRAP_LINE_SIZE - (bannerVersion.length() + DYNAMIC_THREAD_POOL.length())) {
            padding.append(" ");
        }

        System.out.println(AnsiOutput.toString(
                AnsiColor.GREEN, banner,
                AnsiColor.DEFAULT, DYNAMIC_THREAD_POOL,
                AnsiColor.DEFAULT, padding.toString(),
                AnsiStyle.FAINT, bannerVersion,
                "\n" // 根据需要添加换行或链接
        ));
    }
}

pomxml里面加这个

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>1.18.34</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <!-- 2. 新增:Spring Boot 插件(只用于生成构建信息) -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>3.2.0</version>
                <executions>
                    <execution>
                        <goals>
                            <!-- 核心配置:生成 META-INF/build-info.properties -->
                            <goal>build-info</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
     </build>

</project>

基于Nacos开发oneThread控制台模块

在深入了解 DashBoard-Dev 的设计之前,我们先来分析一下市场上动态线程池的两种主流架构模式。

1. 配置中心模式

配置中心模式是一种相对轻量的架构方案:

2. 独立服务模式

独立服务模式提供了更强的功能性,但架构相对复杂:

3. oneThread 混合架构

oneThread 采用了一种创新的混合架构,既保持了配置中心模式的轻量特性,又提供了独立服务模式的可视化能力:

这种混合架构的核心优势在于:

  • 数据源统一 :所有配置变更都通过配置中心进行,保证数据一致性。
  • 依赖最小化 :业务应用只需依赖配置中心,无需额外中间件。
  • 可视化增强 :DashBoard-Dev 提供友好的管理界面。
  • 架构灵活 :可以选择性部署 Dashboard,不影响核心功能。

抽象 Nacos API 操作

在 oneThread 框架中,Nacos 代理客户端(NacosProxyClient)扮演着关键角色。它负责与 Nacos 服务端进行交互,实现配置的查询、更新和服务实例的发现。通过封装Nacos的RESTAPI,NacosProxyClient提供了一组面向业务的高层接口 ,使得框架的其他层能够以统一的方式操作 Nacos,而无需关心底层实现细节。

com.nageoffer.onethread.dashboard.dev.server.remote.client.NacosProxyClient

1. 核心方法设计

NacosProxyClient 包含四个核心方法,分别对应不同的 Nacos REST API 操作:

  • listConfig方法 :查询命名空间下配置文件集合。
publicList<NacosConfigRespDTO>listConfig(String namespace){// ... 省略实现细节}
  • getConfig方法 :查询配置明细信息。
publicNacosConfigDetailRespDTOgetConfig(String namespace,String dataId,String group){// ... 省略实现细节}
  • publishConfig方法 :发布配置。
publicvoidpublishConfig(String namespace,String dataId,String group,StringMD5,String id,String content,String type){// ... 省略实现细节}
  • service方法 :查询服务明细。
publicNacosServiceListRespDTOservice(String namespace,String service){// ... 省略实现细节}

2. 参数处理与命名空间隔离

NacosProxyClient 在处理参数时,特别注意了命名空间的隔离问题。在 Nacos 中,命名空间是用于区分不同环境(如开发、测试、生产)的隔离单元。通过判断namespace是否为"public",NacosProxyClient能够正确处理不同命名空间下的配置请求

// 在listConfig方法中form("tenant",Objects.equals(namespace,"public")?"": namespace)
​
// 在 service方法中form("namespaceId",Objects.equals(namespace,"public")?"": namespace)

这种设计使得 oneThread 框架能够支持多环境部署,实现配置的环境隔离 ,大大提高了框架的灵活性和安全性。

如果是 public 需要传递空字符串,为了适配 Nacos 的接口参数处理。

3. YAML 配置解析与绑定

NacosProxyClient 在查询配置后,需要将配置内容解析为 Java 对象。oneThread框架使用YAML格式存储线程池配置,通过yamlConfigParser进行解析,再通过Spring的Binder绑定到DashBoardConfigProperties对象

// 在 threadPoolManagerService 中Map<Object, Object> configInfoMap = yamlConfigParser.doParse(config.getContent());ConfigurationPropertySource sources =newMapConfigurationPropertySource(configInfoMap);Binder binder =newBinder(sources);DashBoardConfigProperties refresherProperties = binder.bind("onethread",Bindable.of(DashBoardConfigProperties.class)).orElseThrow(()->newIllegalArgumentException("配置绑定失败"));

DashBoard-Dev 服务的功能与实现机制

DashBoard-Dev 服务是 oneThread 框架的管理入口,它提供了一套 REST API,使得运维人员能够通过 HTTP 接口查询和更新线程池配置,监控线程池运行状态,并设置阈值告警。DashBoard-Dev服务的核心功能包括配置管理、服务发现、状态监控和告警设置

DashBoard-Dev 中,如果进行功能拆分,可以划分为以下几个模块:

  • 项目模块
  • Grafana 模块
  • 用户模块
  • 线程池管理 & 实例
  • Web 线程池管理 & 实例

其中,前三个模块主要是为了支撑控制台的搭建,设计相对简单,大家了解即可。

真正的核心在于 线程池管理Web线程池管理 ,二者的逻辑基本一致。下面将以 线程池 为例展开说明。

1. 管理配置 API

DashBoard-Dev 服务通过 ThreadPoolManagerController 提供配置管理 API,允许用户查询和更新线程池配置。

查询线程池集合API

@GetMapping("/api/onethread-dashboard/thread-pools")publicResult<List<ThreadPoolDetailRespDTO>>listThreadPool(ThreadPoolListReqDTO requestParam){returnResults.success(threadPoolManagerService.listThreadPool(requestParam));}

更新线程池API

@PutMapping("/api/onethread-dashboard/thread-pool")publicResult<Void>updateGlobalThreadPool(@RequestBody@ValidThreadPoolUpdateReqDTO requestParam){
    threadPoolManagerService.updateGlobalThreadPool(requestParam);returnResults.success();}

这些 API 调用 threadPoolManagerService 的相应方法,通过 NacosProxyClient 与 Nacos 交互,实现配置的查询和更新。

2. 服务发现与配置聚合

DashBoard-Dev 服务通过 NacosProxyClient 的 service 方法查询服务实例列表,然后通过 HTTP 调用各实例的监控接口,收集线程池运行时状态。

服务发现与配置聚合流程

  1. 1.根据命名空间和服务名查询 Nacos 服务实例列表;
  2. 2.对每个服务实例发起 HTTP 请求,获取线程池运行时状态;
  3. 3.将各实例的状态数据聚合,形成全局的线程池监控视图。
// 在 threadPoolManagerService 的 listThreadPool 方法中List<String> namespaces =newArrayList<>(oneThreadProperties.getNamespaces());// 处理namespace过滤// 并行拉取各namespace的配置List<Map.Entry<String, NacosConfigRespDTO>> tasks = namespaces
        .parallelStream().flatMap(ns ->{List<NacosConfigRespDTO> cfgs = nacosProxyClient.listConfig(ns);// ... 省略过滤逻辑}).collect(Collectors.toList());
​
// 并行处理任务:解析YAML -> 绑定配置 -> 查询服务实例数 -> 拼装返回return tasks.parallelStream().map(entry ->{String namespace = entry.getKey();NacosConfigRespDTO config = entry.getValue();
​
            // 解析YAMLMap<Object, Object> configInfoMap = yamlConfigParser.doParse(config.getContent());ConfigurationPropertySource sources =newMapConfigurationPropertySource(configInfoMap);Binder binder =newBinder(sources);
​
            // 绑定onethread配置DashBoardConfigProperties refresherProperties = binder.bind("onethread",Bindable.of(DashBoardConfigProperties.class)).orElseThrow(()->newIllegalArgumentException("配置绑定失败"));
​
            // 查询当前服务在Nacos的实例数NacosServiceListRespDTO service = nacosProxyClient.service(namespace, config.getAppName());
            refresherProperties.getExecutors().forEach(each ->{
                each.setNamespace(namespace);
                each.setServiceName(config.getAppName());
                each.setDataId(config.getDataId());
                each.setGroup(config.getGroup());
                each.setInstanceCount(service.getInstanceCount());});
​
            return refresherProperties.getExecutors();}).flatMap(List::stream).collect(Collectors.toList());

这一实现利用了Java8的StreamAPI和并行流,提高了配置查询和状态收集的效率 。特别是对于大规模部署的系统,这种并行处理机制能够显著减少管理界面的响应时间。

3. 线程池动态调整实现

DashBoard-Dev 服务通过 updateGlobalThreadPool 方法实现线程池参数的动态调整。这一过程涉及到配置的查询、修改、解析和发布,最终实现线程池参数的在线更新

线程池参数热更新流程

  1. 1.DashBoard-Dev 服务通过 NacosProxyClient 更新线程池配置;
  2. 2.Nacos 配置中心将配置变更推送给所有监听该配置的业务应用;
  3. 3.业务应用通过 oneThread SpringBoot Starter 配置监听器捕获配置变更;
  4. 4.业务应用解析新配置,更新本地线程池参数。
@SneakyThrows@OverridepublicvoidupdateGlobalThreadPool(ThreadPoolUpdateReqDTO requestParam){// 查询原配置NacosConfigDetailRespDTO configDetail = nacosProxyClient.getConfig(requestParam.getNamespace(), requestParam.getDataId(), requestParam.getGroup());String originalContent = configDetail.getContent();
​
    // 解析原配置Map<Object, Object> configInfoMap = yamlConfigParser.doParse(originalContent);ConfigurationPropertySource source =newMapConfigurationPropertySource(configInfoMap);
​
    // 绑定到Java对象DashBoardConfigProperties onethread = binder.bind("onethread",Bindable.of(DashBoardConfigProperties.class)).orElseThrow(()->newRuntimeException("绑定失败"));
​
    // 修改线程池参数
    onethread.getExecutors().stream().filter(e -> e.getThreadPoolId().equals(requestParam.getThreadPoolId())).findFirst().ifPresent(e ->{
                e.setCorePoolSize(requestParam.getCorePoolSize());
                e.setMaximumPoolSize(requestParam.getMaximumPoolSize());
                e.setKeepAliveTime(requestParam.getKeepAliveTime());
                e.setQueueCapacity(requestParam.getQueueCapacity());// ... 省略其他参数设置});
​
    // 将Java对象转回YAML格式YAMLFactory factory =newYAMLFactory();
    factory.disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
    factory.enable(YAMLGenerator.Feature.MINIMIZEQuOTES);
​
    ObjectMapper objectMapper =newObjectMapper(factory);
    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    objectMapper.setPropertyNamingStrategy PropertyNamingStrategies.KEBAB_CASE);
​
    String yamlStr = objectMapper.writeValueAsString(Collections.singletonMap("onethread", onethread));
​
    // 发布新配置
    nacosProxyClient.publishConfig(requestParam.getNamespace(), requestParam.getDataId(), requestParam.getGroup(), configDetail.getAppName(), configDetail.getId(), configDetail.getMD5(), yamlStr,"yaml");}

这一实现流程的关键在于获取原配置的id和md5值 ,以确保配置更新的原子性和一致性。在 Nacos 中,每个配置都有唯一的 id 和 md5 值,通过携带这些值进行配置更新,可以避免多人同时修改配置时的版本冲突问题。

4. 线程池实例监控指标

线程池监控能力由 onethread-dashboard-dev-spring-boot-starter 提供,它暴露了标准的 Web 接口。 底层实现依赖业务应用在 Nacos 上注册的 IP地址 ,Dashboard 通过该地址直接调用目标应用的接口获取线程池状态。

例如,getRuntimeState 方法会拼接业务应用的网络地址并发起请求:

@OverridepublicThreadPoolStateRespDTOgetRuntimeState(String threadPoolId,String networkAddress){String resultStr =HttpUtil.get("http://"+ networkAddress +"/dynamic/thread-pool/"+ threadPoolId);Result<ThreadPoolStateRespDTO> result =JSON.parseObject(resultStr,newTypeReference<>(){});return result.getData();}

其中 networkAddress 参数来源于线程池列表接口,调用时直接透传。

onethread-dashboard-dev-spring-boot-starter 内置的控制器中,提供了两类接口:

  • 轻量级指标接口 :用于线程池列表展示,快速获取基础运行信息;
  • 完整运行时接口 :用于详情页面,展示线程池的全面状态。

代码如下所示:

@RestController@RequiredArgsConstructorpublicclassDynamicThreadPoolController{
​
    privatefinalDynamicThreadPoolService dynamicThreadPoolService;
​
    /**
     * 获取线程池的轻量级运行指标
     */@GetMapping("/dynamic/thread-pool/{threadPoolId}/basic-metrics")publicResult<ThreadPoolDashBoardDevBaseMetricsRespDTO>getBasicMetrics(@PathVariableString threadPoolId){returnResults.success(dynamicThreadPoolService.getBasicMetrics(threadPoolId));}
​
    /**
     * 获取线程池的完整运行时状态
     */@GetMapping("/dynamic/thread-pool/{threadPoolId}")publicResult<ThreadPoolDashBoardDevRespDTO>getRuntimeInfo(@PathVariableString threadPoolId){returnResults.success(dynamicThreadPoolService.getRuntimeInfo(threadPoolId));}}

5. 线程池管理和实例有什么区别?

线程池管理 中,分为 线程池列表线程池实例 两个维度:

  • 线程池列表 “代表当前的静态线程池配置,配置变更会作用于 所有应用实例 。可以理解为全局的线程池配置管理。
  • 线程池实例 :展示了某个线程池在不同应用实例上的运行情况。支持查看每个实例的详细运行参数,并且可以针对单个实例进行参数调整。

单实例变更场景 :通常用于集群整体线程池配置没有问题,但某个实例出现了特殊情况(如遇到流量激增)。此时无需修改全局配置,只需扩容或调整该实例的线程池参数即可。

维度线程池列表线程池实例
定义静态的线程池配置清单某个线程池在具体应用实例上的运行情况
作用范围变更作用于 所有应用实例可以针对 单个实例 调整参数
用途全局统一的线程池配置管理针对单实例的运行状态监控与调整
可查看内容配置项(核心线程数、最大线程数、队列大小等)每个实例的实时运行指标与配置详情
典型场景统一配置、批量修改某个实例异常,需要扩容或单独调整参数

文末总结

oneThread 动态线程池框架的 DashBoard-Dev 服务,通过巧妙的架构设计,成功地平衡了系统复杂度与功能完整性。它既保持了基于配置中心架构的轻量特性,又提供了强大的可视化管理能力,为线程池治理提供了一套完整的解决方案。

核心设计亮点总结

  1. 1.架构创新 :采用配置中心 + 可视化代理的混合模式,兼顾轻量与功能性。
  2. 2.实现精巧 :通过 Nacos HTTP API 代理,实现了无侵入的配置管理。
  3. 3.性能优化 :大量使用并行处理和异步编程,提升了数据获取和处理效率。
  4. 4.扩展友好 :预留了多个扩展点,支持多配置中心和自定义监控指标。

这种设计思路对于其他中间件的可视化管理也具有很好的借鉴意义,特别是在如何平衡架构复杂度与功能完整性方面,算是为大家提供了一个开阔性的案例吧。