• 使用nacos实现简单的动态化线程池


    1.背景

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

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

    2.code

    1.依赖

    1. <dependency>
    2.    <groupId>com.alibaba.cloudgroupId>
    3.    <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    4.    <version>2021.1version>
    5. dependency>
    6. <dependency>
    7.    <groupId>com.alibaba.cloudgroupId>
    8.    <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
    9.    <version>2021.1version>
    10. dependency>
    11. <dependency>
    12.    <groupId>org.springframework.bootgroupId>
    13.    <artifactId>spring-boot-starter-webartifactId>
    14. dependency>
    15. <dependency>
    16.    <groupId>org.springframework.bootgroupId>
    17.    <artifactId>spring-boot-starterartifactId>
    18. dependency>

    2.配置yml文件

    bootstrap.yml

    1. server:
    2. port: 8010
    3.  # 应用名称(nacos会将该名称当做服务名称)
    4. spring:
    5. application:
    6.   name: order-service
    7. cloud:
    8.   nacos:
    9.     discovery:
    10.       namespace: public
    11.       server-addr: 192.168.174.129:8848
    12.     config:
    13.       server-addr: 192.168.174.129:8848
    14.       file-extension: yml

    application.yml

    1. spring:
    2. profiles:
    3.   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配置变更监听

    1. @RefreshScope
    2. @Configuration
    3. public class DynamicThreadPool implements InitializingBean {
    4. @Value("${core.size}")
    5. private String coreSize;
    6. @Value("${max.size}")
    7. private String maxSize;
    8. private static ThreadPoolExecutor threadPoolExecutor;
    9. @Autowired
    10. private NacosConfigManager nacosConfigManager;
    11. @Autowired
    12. private NacosConfigProperties nacosConfigProperties;
    13. @Override
    14. public void afterPropertiesSet() throws Exception {
    15. //按照nacos配置初始化线程池
    16. threadPoolExecutor = new ThreadPoolExecutor(Integer.parseInt(coreSize), Integer.parseInt(maxSize), 10L, TimeUnit.SECONDS,
    17. new LinkedBlockingQueue<>(10),
    18. new ThreadFactoryBuilder().setNameFormat("c_t_%d").build(),
    19. new RejectedExecutionHandler() {
    20. @Override
    21. public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
    22. System.out.println("rejected!");
    23. }
    24. });
    25. //nacos配置变更监听
    26. nacosConfigManager.getConfigService().addListener("order-service-dev.yml", nacosConfigProperties.getGroup(),
    27. new Listener() {
    28. @Override
    29. public Executor getExecutor() {
    30. return null;
    31. }
    32. @Override
    33. public void receiveConfigInfo(String configInfo) {
    34. //配置变更,修改线程池配置
    35. System.out.println(configInfo);
    36. changeThreadPoolConfig(Integer.parseInt(coreSize), Integer.parseInt(maxSize));
    37. }
    38. });
    39. }
    40. /**
    41. * 打印当前线程池的状态
    42. */
    43. public String printThreadPoolStatus() {
    44. return String.format("core_size:%s,thread_current_size:%s;" +
    45. "thread_max_size:%s;queue_current_size:%s,total_task_count:%s", threadPoolExecutor.getCorePoolSize(),
    46. threadPoolExecutor.getActiveCount(), threadPoolExecutor.getMaximumPoolSize(), threadPoolExecutor.getQueue().size(),
    47. threadPoolExecutor.getTaskCount());
    48. }
    49. /**
    50. * 给线程池增加任务
    51. *
    52. * @param count
    53. */
    54. public void dynamicThreadPoolAddTask(int count) {
    55. for (int i = 0; i < count; i++) {
    56. int finalI = i;
    57. threadPoolExecutor.execute(new Runnable() {
    58. @Override
    59. public void run() {
    60. try {
    61. System.out.println(finalI);
    62. Thread.sleep(10000);
    63. } catch (InterruptedException e) {
    64. e.printStackTrace();
    65. }
    66. }
    67. });
    68. }
    69. }
    70. /**
    71. * 修改线程池核心参数
    72. *
    73. * @param coreSize
    74. * @param maxSize
    75. */
    76. private void changeThreadPoolConfig(int coreSize, int maxSize) {
    77. threadPoolExecutor.setCorePoolSize(coreSize);
    78. threadPoolExecutor.setMaximumPoolSize(maxSize);
    79. }
    80. }

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

    @RefreshScope,这个注解用来支持nacos的动态刷新功能;

    @Value("${max.size}"),@Value("${core.size}"),这两个注解用来读取我们上一步在nacos配置的具体信息;同时,nacos配置变更时,能够实时读取到变更后的内容;

    nacosConfigManager.getConfigService().addListener,配置监听,nacos配置变更时实时修改线程池的配置。

    5.controller

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

    1. @RestController
    2. @RequestMapping("/threadpool")
    3. public class ThreadPoolController {
    4. @Autowired
    5. private DynamicThreadPool dynamicThreadPool;
    6. /**
    7. * 打印当前线程池的状态
    8. */
    9. @GetMapping("/print")
    10. public String printThreadPoolStatus() {
    11. return dynamicThreadPool.printThreadPoolStatus();
    12. }
    13. /**
    14. * 给线程池增加任务
    15. *
    16. * @param count
    17. */
    18. @GetMapping("/add")
    19. public String dynamicThreadPoolAddTask(int count) {
    20. dynamicThreadPool.dynamicThreadPoolAddTask(count);
    21. return String.valueOf(count);
    22. }
    23. }

    6.测试

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

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

     访问http://localhost:8010/threadpool/add?count=20增加20个任务。

    重新打印线程池配置

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

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

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

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

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

     3.总结

    这里,只是简单实现了一个可以调整核心线程数和最大线程数的动态线程池。具体的线程池实现原理可以参考美团的这篇文章Java线程池实现原理及其在美团业务中的实践 - 美团技术团队,结合监控告警等实现一个完善的动态线程池产品。

    一个开源的动态线程池实现:GitHub - dromara/dynamic-tp: 🔥🔥🔥轻量级动态线程池,内置监控告警功能,集成三方中间件线程池管理,基于主流配置中心(已支持Nacos、Apollo,Zookeeper、Consul、Etcd,可通过SPI自定义实现)。Lightweight dynamic threadpool, with monitoring and alarming functions, base on popular config centers (already support Nacos、Apollo、Zookeeper、Consul, can be customized through SPI).

  • 相关阅读:
    Redis中的慢查询日志(一)
    动态内存开辟(上)
    数据库访问中间件--springdata-jpa的基本使用
    Goby 漏洞发布|深信服下一代防火墙 NGAF login.cgi 文件远程命令执行漏洞
    VSCode中打开md文件的智能提示
    mac安装Nginx&Nginx常见的命令&Nginx反向代理、负载均衡
    Stable Diffusion 模型分享:CG texture light and shadow(CG纹理光影)
    好心情:哪些情况需要送医至精神科急诊?
    2022.8.13
    论文研读2——对抗样本(Adversarial Example)综述(2021版)
  • 原文地址:https://blog.csdn.net/m0_37558251/article/details/126542359