开发喵星球

强烈推荐:SpringBoot 调用 Nacos 实现动态化线程池,无比香!

在后台开发中,会经常用到线程池技术,对于线程池核心参数的配置很大程度上依靠经验。然而,由于系统运行过程中存在的不确定性,我们很难一劳永逸地规划一个合理的线程池参数。

在对线程池配置参数进行调整时,一般需要对服务进行重启,这样修改的成本就会偏高。一种解决办法就是,将线程池的配置放到平台侧,运行开发同学根据系统运行情况对核心参数进行动态配置。

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

一、注册aliyun Nacos

本机 https://github.com/alibaba/nacos

阿里云注册Nacos

https://www.aliyun.com/product/aliware/mse?spm=nacos-website.topbar.0.0.0

image-20231107140836436

image-20231107140925055

nacos配置

image-20231107141009654

注意网络设置

一定要配置公网

image-20231107141104709

配置命名空间

image-20231107141222100

image-20231107103824103

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

二、代码实现

目录结构

image-20231107141510284

1、依赖

Spring-boot 配置

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.17</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

spring-cloud

spring-cloud-starter-alibaba-nacos-discovery

spring-cloud-starter-alibaba-nacos-config

spring-cloud-starter-bootstrap

               <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2021.0.1.0</version>
        </dependency>
          
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>2021.0.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
            <version>3.1.0</version>
        </dependency>

2、配置yml文件

bootstrap.yml:

server:
  port: 8010
  # 应用名称(nacos会将该名称当做服务名称)
spring:
  application:
    name: order-service
  cloud:
    nacos:
      discovery:
        namespace: public
        server-addr: mse-67495b00-p.nacos-ans.mse.aliyuncs.com:8848
      config:
        server-addr: mse-67495b00-p.nacos-ans.mse.aliyuncs.com:8848
        file-extension: yml

application.yml:

spring:
  application:
    name: order-service
  profiles:
    active: dev

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

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

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

3、线程池配置和nacos配置变更监听

DynamicThreadPool.java

package com.kaifamiao.sp_nacos_threadpool;

import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.nacos.api.config.listener.Listener;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.*;

@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);
    }
}

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

4、控制器Controller

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

package com.kaifamiao.sp_nacos_threadpool;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@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);
    }
}

5、测试

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

image-20231107141957122

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

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

image-20231107142026739

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

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

image-20231107142125161

如果线程不够可以到阿里云后台调整,动态调整nacos配置,将核心线程数调整为50,最大线程数调整为100.

总结

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

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

相关代码:https://gitee.com/kaifamiaos/sp_nacos_threadpool

注意避坑

springboot和nacos有个版本兼容问题

yml文件格式一定要正确,否则是127.0.0.1

aliyun的nacos一定网络配置公网方式

   
分类:Java/OOP 作者:开发喵 发表于:2023-11-07 14:25:04 阅读量:123
<<   >>


powered by kaifamiao