利用 Nacos 实现了一个动态化线程池,非常实用!

简介: 利用 Nacos 实现了一个动态化线程池,非常实用!



在后台开发中,会经常用到线程池技术,对于线程池核心参数的配置很大程度上依靠经验。然而,由于系统运行过程中存在的不确定性,我们很难一劳永逸地规划一个合理的线程池参数。在对线程池配置参数进行调整时,一般需要对服务进行重启,这样修改的成本就会偏高。一种解决办法就是,将线程池的配置放到平台侧,运行开发同学根据系统运行情况对核心参数进行动态配置。

本文以Nacos作为服务配置中心,以修改线程池核心线程数、最大线程数为例,实现一个简单的动态化线程池。

代码实现

1.依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>2021.1</version>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    <version>2021.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

2.配置yml文件

bootstrap.yml:

server:
  port: 8010
  # 应用名称(nacos会将该名称当做服务名称)
spring:
  application:
    name: order-service
  cloud:
    nacos:
      discovery:
        namespace: public
        server-addr: 192.168.174.129:8848
      config:
        server-addr: 192.168.174.129:8848
        file-extension: yml

application.yml:

spring:
  profiles:
    active: dev

为什么要配置两个yml文件?

springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application。

nacos在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后才能保证项目的正常启动。

3.nacos配置

登录到nacos管理页面,新建配置,如下图所示:

注意Data ID的命名格式为,${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension} ,在本文中,Data ID的名字就是order-service-dev.yml

这里我们只配置了两个参数,核心线程数量和最大线程数。

4.线程池配置和nacos配置变更监听

@RefreshScope
@Configuration
public class DynamicThreadPool implements InitializingBean {
    @Value("${core.size}")
    private String coreSize;
    @Value("${max.size}")
    private String maxSize;
    private static ThreadPoolExecutor threadPoolExecutor;
    @Autowired
    private NacosConfigManager nacosConfigManager;
    @Autowired
    private NacosConfigProperties nacosConfigProperties;
    @Override
    public void afterPropertiesSet() throws Exception {
        //按照nacos配置初始化线程池
        threadPoolExecutor = new ThreadPoolExecutor(Integer.parseInt(coreSize), Integer.parseInt(maxSize), 10L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10),
                new ThreadFactoryBuilder().setNameFormat("c_t_%d").build(),
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        System.out.println("rejected!");
                    }
                });
        //nacos配置变更监听
        nacosConfigManager.getConfigService().addListener("order-service-dev.yml", nacosConfigProperties.getGroup(),
                new Listener() {
                    @Override
                    public Executor getExecutor() {
                        return null;
                    }
                    @Override
                    public void receiveConfigInfo(String configInfo) {
                        //配置变更,修改线程池配置
                        System.out.println(configInfo);
                        changeThreadPoolConfig(Integer.parseInt(coreSize), Integer.parseInt(maxSize));
                    }
                });
    }
    /**
     * 打印当前线程池的状态
     */
    public String printThreadPoolStatus() {
        return String.format("core_size:%s,thread_current_size:%s;" +
                        "thread_max_size:%s;queue_current_size:%s,total_task_count:%s", threadPoolExecutor.getCorePoolSize(),
                threadPoolExecutor.getActiveCount(), threadPoolExecutor.getMaximumPoolSize(), threadPoolExecutor.getQueue().size(),
                threadPoolExecutor.getTaskCount());
    }
    /**
     * 给线程池增加任务
     *
     * @param count
     */
    public void dynamicThreadPoolAddTask(int count) {
        for (int i = 0; i < count; i++) {
            int finalI = i;
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(finalI);
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
    /**
     * 修改线程池核心参数
     *
     * @param coreSize
     * @param maxSize
     */
    private void changeThreadPoolConfig(int coreSize, int maxSize) {
        threadPoolExecutor.setCorePoolSize(coreSize);
        threadPoolExecutor.setMaximumPoolSize(maxSize);
    }
}

这个代码就是实现动态线程池和核心了,需要说明的是:

  • @RefreshScope:这个注解用来支持nacos的动态刷新功能;
  • @Value("${max.size}")@Value("${core.size}"):这两个注解用来读取我们上一步在nacos配置的具体信息;同时,nacos配置变更时,能够实时读取到变更后的内容
  • nacosConfigManager.getConfigService().addListener:配置监听,nacos配置变更时实时修改线程池的配置。

5.controller

为了观察线程池动态变更的效果,增加Controller类。

@RestController
@RequestMapping("/threadpool")
public class ThreadPoolController {
    @Autowired
    private DynamicThreadPool dynamicThreadPool;
    /**
     * 打印当前线程池的状态
     */
    @GetMapping("/print")
    public String printThreadPoolStatus() {
        return dynamicThreadPool.printThreadPoolStatus();
    }
    /**
     * 给线程池增加任务
     *
     * @param count
     */
    @GetMapping("/add")
    public String dynamicThreadPoolAddTask(int count) {
        dynamicThreadPool.dynamicThreadPoolAddTask(count);
        return String.valueOf(count);
    }
}

6.测试

启动项目,访问http://localhost:8010/threadpool/print打印当前线程池的配置。

可以看到,这个就是我们之前在nacos配置的线程数。

访问http://localhost:8010/threadpool/add?count=20增加20个任务,重新打印线程池配置

可以看到已经有线程在排队了。

为了能够看到效果,我们多访问几次/add接口,增加任务数,在控制台出现拒绝信息时调整nacos配置。

此时,执行/add命令时,所有的线程都会提示rejected。

调整nacos配置,将核心线程数调整为50,最大线程数调整为100.

重新多次访问/add接口增加任务,发现没有拒绝信息了。这时,打印具体的线程状态,发现线程池参数修改成功。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

总结

这里,只是简单实现了一个可以调整核心线程数和最大线程数的动态线程池。具体的线程池实现原理可以参考美团的这篇文章:https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html,结合监控告警等实现一个完善的动态线程池产品。

优秀的轮子还有好多,比如Hippo4J ,使用起来和dynamic-tp差不多。Hippo4J 有无依赖中间件实现动静线程池,也有默认实现Nacos和Apollo的版本,而dynamic-tp 默认实现依赖Nacos或Apollo。




相关文章
|
7月前
|
运维 负载均衡 Java
nacos常见问题之单机nacos2.2.3线程池异常如何解决
Nacos是阿里云开源的服务发现和配置管理平台,用于构建动态微服务应用架构;本汇总针对Nacos在实际应用中用户常遇到的问题进行了归纳和解答,旨在帮助开发者和运维人员高效解决使用Nacos时的各类疑难杂症。
|
消息中间件 缓存 资源调度
【Java项目】使用Nacos实现动态线程池技术以及Nacos配置文件更新监听事件
【Java项目】使用Nacos实现动态线程池技术以及Nacos配置文件更新监听事件
398 0
|
5月前
|
Java Nacos 数据库
使用 nacos 搭建注册中心及配置中心
使用 nacos 搭建注册中心及配置中心
102 5
|
5月前
|
NoSQL Java Nacos
SpringCloud集成Seata并使用Nacos做注册中心与配置中心
SpringCloud集成Seata并使用Nacos做注册中心与配置中心
153 3
|
24天前
|
负载均衡 应用服务中间件 Nacos
Nacos配置中心
Nacos配置中心
52 1
Nacos配置中心
|
20天前
|
监控 Java 测试技术
Nacos 配置中心变更利器:自定义标签灰度
本文是对 MSE Nacos 应用自定义标签灰度的功能介绍,欢迎大家升级版本进行试用。
|
23天前
|
网络安全 Nacos 开发者
Nacos作为流行的微服务注册与配置中心,“节点提示暂时不可用”是常见的问题之一
Nacos作为流行的微服务注册与配置中心,其稳定性和易用性备受青睐。然而,“节点提示暂时不可用”是常见的问题之一。本文将探讨该问题的原因及解决方案,帮助开发者快速定位并解决问题,确保服务的正常运行。通过检查服务实例状态、网络连接、Nacos配置、调整健康检查策略等步骤,可以有效解决这一问题。
34 4
|
23天前
|
Java 网络安全 Nacos
Nacos作为流行的微服务注册与配置中心,其稳定性和易用性备受青睐。
Nacos作为流行的微服务注册与配置中心,其稳定性和易用性备受青睐。然而,实际使用中常遇到“客户端不发送心跳检测”的问题。本文深入探讨该问题的原因及解决方案,帮助开发者快速定位并解决问题,确保服务正常运行。通过检查客户端配置、网络连接、日志、版本兼容性、心跳策略、注册状态、重启应用和环境变量等步骤,系统地排查和解决这一问题。
45 3
|
23天前
|
安全 Nacos 数据库
Nacos是一款流行的微服务注册与配置中心,但直接暴露在公网中可能导致非法访问和数据库篡改
Nacos是一款流行的微服务注册与配置中心,但直接暴露在公网中可能导致非法访问和数据库篡改。本文详细探讨了这一问题的原因及解决方案,包括限制公网访问、使用HTTPS、强化数据库安全、启用访问控制、监控和审计等步骤,帮助开发者确保服务的安全运行。
31 3
|
1月前
|
SQL 关系型数据库 数据库连接
"Nacos 2.1.0版本数据库配置写入难题破解攻略:一步步教你排查连接、权限和配置问题,重启服务轻松解决!"
【10月更文挑战第23天】在使用Nacos 2.1.0版本时,可能会遇到无法将配置信息写入数据库的问题。本文将引导你逐步解决这一问题,包括检查数据库连接、用户权限、Nacos配置文件,并提供示例代码和详细步骤。通过这些方法,你可以有效解决配置写入失败的问题。
56 0