Spring Cloud Alibaba - 利用Nacos实现高效动态线程池管理

在这里插入图片描述


引言

Java线程池实现原理及其在美团业务中的实践

开源组件:Hippo4J 、 dynamic-tp


概述

随着现代应用程序的复杂性不断增加,动态线程池管理成为了构建可靠和高效系统的关键之一。而Nacos作为一个优秀的服务发现和配置中心,能够帮助我们实现动态线程池的灵活管理。

我们这里将介绍如何利用Nacos来实现高效的动态线程池管理,并探讨其在分布式系统中的应用。

什么是动态线程池

在软件开发中,线程池是一种管理和重用线程的机制,它能够有效地控制并发执行的线程数量,避免资源耗尽和性能下降。动态线程池则是在传统线程池的基础上,能够根据系统负载和资源需求动态调整线程数量的一种改进型线程池管理方式。


Nacos简介

Nacos是阿里巴巴开源的一款多功能的服务发现和配置中心,它提供了服务注册与发现、动态配置管理、服务路由等功能,被广泛应用于微服务架构中。其优势在于高可用、易扩展、支持多种注册中心和配置源等特性。


如何利用Nacos实现动态线程池管理

  1. 服务注册与发现:首先,将应用程序注册到Nacos服务注册中心,使其成为Nacos的服务实例。这样,我们就可以利用Nacos的服务发现功能来获取可用的服务实例列表。

  2. 动态配置管理:借助Nacos的动态配置管理功能,我们可以将线程池的配置信息存储在Nacos的配置中心中,例如线程池大小、核心线程数、最大线程数、线程存活时间等参数。

  3. 监听配置变更:通过监听Nacos配置中心的变更通知,当线程池配置发生改变时,我们可以及时感知并动态调整线程池的参数,从而实现线程池的动态管理。

  4. 实时监控与调整:结合Nacos的服务监控和告警功能,可以实现对线程池运行状态的实时监控,当发现异常或负载过高时,及时进行调整,保障系统的稳定性和性能。


应用场景

利用Nacos实现动态线程池管理适用于各种需要动态调整线程数量的场景,特别适用于:

  • 微服务架构下的服务调用和资源管理;
  • Web服务器、消息队列等高并发场景的应用;
  • 云计算环境下的资源弹性伸缩。

Code

在这里插入图片描述

版本说明

:: Spring Boot ::                             (v2.4.1)
spring-cloud-starter-alibaba-nacos-discovery   2021.1
spring-cloud-starter-alibaba-nacos-config      2021.1

POM

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>boot2</artifactId>
        <groupId>com.artisan</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <groupId>com.artisan</groupId>
    <artifactId>boot-threadpool-nacos</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>boot-threadpool-nacos</name>
    <description>boot-threadpool-nacos</description>
    <properties>
        <java.version>8</java.version>
    </properties>
    <dependencies>

        <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>

        <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.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-bootstrap -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
            <version>3.1.8</version>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


配置文件

bootstrap.yml

server:
  port: 9090
  # 应用名称(会注册到Nacos上)
spring:
  application:
    name: artisan-service
  cloud:
    nacos:
      discovery:
        namespace: public
        server-addr: 127.0.0.1:8848
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yml

application.yml

spring:
  profiles:
    active: dev

Nacos Config配置文件加载顺序

在使用Spring Boot结合Nacos作为配置中心时,配置文件的加载顺序是非常重要的。一般来说,Spring Boot应用会先加载bootstrap.yml(或bootstrap.properties),再加载application.yml(或application.properties)。下面我们来详细解释这个加载顺序。

1. bootstrap.yml的加载

当Spring Boot应用启动时,首先会加载bootstrap.yml(或bootstrap.properties)文件。这个文件通常用于一些系统级别的配置,例如日志系统的配置、应用的环境变量配置等。在使用Nacos作为配置中心时,可以在bootstrap.yml中配置Nacos的相关参数,如Nacos的服务地址、命名空间等。

2. application.yml的加载

接着,Spring Boot会加载application.yml(或application.properties)文件。这个文件包含了应用程序的业务逻辑相关的配置,如数据库连接信息、端口配置、服务注册信息等。

注意事项
  • 加载顺序优先级: 在Spring Boot中,bootstrap.yml的加载优先级高于application.yml。这意味着如果在bootstrap.yml中配置了某些参数,而在application.yml中也有相同的配置项,那么以bootstrap.yml中的配置为准。
  • Nacos配置优先级: 如果将Nacos作为配置中心,那么Nacos中的配置会覆盖本地配置文件(如bootstrap.yml、application.yml)中的配置。因此,建议将系统级别的配置放在bootstrap.yml中,并将业务逻辑相关的配置放在Nacos中,以便实现配置的分离管理和动态更新。
示例

下面是一个简单的示例,展示了bootstrap.yml和application.yml的配置内容:

# bootstrap.yml
spring:
  application:
    name: my-application
  cloud:
    nacos:
      config:
        server-addr: localhost:8848
        namespace: your-namespace
# application.yml
server:
  port: 8080

在这个示例中,bootstrap.yml中配置了Nacos的服务地址和命名空间,而application.yml中配置了应用程序的端口号。


nacos配置

在这里插入图片描述

在这里插入图片描述
我们目前只配置了两个参数,核心线程数量和最大线程数。

Data Id

Nacos中Data ID的命名格式解释

在Nacos中,Data ID是用来唯一标识配置信息的,而其命名格式一般遵循一定的规则,以便于系统的管理和查找。

${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}就是一种常见的Data ID命名格式

${spring.application.name}

${spring.application.name}表示Spring Boot应用的名称,它是在application.propertiesapplication.yml中通过spring.application.name属性进行配置的。这个属性通常用来标识应用的名称,用于服务注册、日志输出等场景。

${spring.profile.active}

${spring.profile.active}表示Spring Boot应用当前的Profile,它是通过spring.profiles.active属性进行配置的。Profile用于区分不同环境下的配置信息,例如devtestprod等,可以根据实际情况进行配置。

${spring.cloud.nacos.config.file-extension}

${spring.cloud.nacos.config.file-extension}表示配置文件的扩展名,它是通过spring.cloud.nacos.config.file-extension属性进行配置的。在Nacos中,配置信息可以是properties格式或yaml格式的文件,通过指定扩展名来区分不同类型的配置文件,如.properties.yaml

合并后的Data ID

将上述三部分内容按照规定的格式进行合并,就得到了Nacos中Data ID的命名格式:${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}。这个命名格式能够唯一标识一个配置文件,并且包含了应用名称、Profile和配置文件类型等信息,方便系统根据需要进行查找和加载。

示例

假设我们有一个名为my-application的Spring Boot应用,当前处于dev环境,并且使用properties格式的配置文件,那么对应的Data ID就是:my-application-dev.properties

那我们这里的就是 artisan-service-dev.yml

在这里插入图片描述


Configuration & Change Callback

package com.artisan.bootthreadpoolnacos.config;

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 lombok.extern.slf4j.Slf4j;
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.*;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@RefreshScope
@Configuration
@Slf4j
public class DynamicThreadPoolConfig 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) {
                        log.info("Warn Warn Warn : rejected executed!!!");
                    }
                });

        //nacos配置变更监听
        nacosConfigManager.getConfigService().addListener("artisan-service-dev.yml", nacosConfigProperties.getGroup(),
                new Listener() {
                    @Override
                    public Executor getExecutor() {
                        return null;
                    }

                    @Override
                    public void receiveConfigInfo(String configInfo) {
                        //配置变更,修改线程池配置
                        log.info("收到Nacos Config Server推来的配置变更,修改线程池配置", 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(() -> {
                try {
                    log.info("dynamicThreadPoolAddTask->", 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);
    }
}
    

动态线程池的配置类,主要功能包括根据Nacos配置动态调整线程池的核心参数,并监听Nacos配置的变化。

  • @RefreshScope是Spring Cloud中的一个注解,用于实现配置信息的热更新。当使用Nacos作为配置中心时,结合@RefreshScope注解可以实现配置信息的动态刷新,即在配置信息发生变化时,应用程序可以动态地重新加载最新的配置信息,而不需要重启应用。 在上述代码中,@RefreshScope注解被用于配置类DynamicThreadPoolConfig上。这意味着当Nacos中的配置信息发生变化时,Spring容器会自动检测到变化,并重新创建被@RefreshScope注解修饰的Bean,以便应用程序能够获取到最新的配置信息。 通过结合@RefreshScope注解和Nacos配置中心,可以实现应用程序在运行时动态地响应配置变化,从而实现更加灵活和可维护的系统配置管理。
  • core.sizemax.size是从Nacos中获取的配置参数,分别代表线程池的核心线程数和最大线程数。
  • afterPropertiesSet方法是InitializingBean接口的实现方法,在Bean属性设置后被调用。在这个方法中,初始化了线程池,并注册了Nacos配置的监听器。
  • printThreadPoolStatus方法用于打印当前线程池的状态信息。
  • dynamicThreadPoolAddTask方法用于向线程池中添加任务。
  • changeThreadPoolConfig方法用于修改线程池的核心参数,例如核心线程数和最大线程数。

测试

package com.artisan.bootthreadpoolnacos.controller;

import com.artisan.bootthreadpoolnacos.config.DynamicThreadPoolConfig;
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;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */

@RestController
@RequestMapping("/dtp")
public class ThreadPoolController {

    @Autowired
    private DynamicThreadPoolConfig dynamicThreadPool;

    /**
     * 打印当前线程池的状态
     */
    @GetMapping("/info")
    public String printThreadPoolStatus() {
        return dynamicThreadPool.printThreadPoolStatus();
    }

    /**
     * 给线程池增加任务
     *
     * @param count
     */
    @GetMapping("/add")
    public String dynamicThreadPoolAddTask(int count) {
        dynamicThreadPool.dynamicThreadPoolAddTask(count);
        return String.valueOf(count);
    }
}
    
  • 启动Nacos server
  • 启动应用

在这里插入图片描述

打印基本信息

在这里插入图片描述

增加任务,观察执行情况

http://localhost:9090/dtp/add?count=100

日志输出

在这里插入图片描述

Nacos上修改配置信息

在这里插入图片描述

观察日志和信息

在这里插入图片描述

在这里插入图片描述


小结

通过本文的介绍,我们了解了如何利用Nacos实现高效的动态线程池管理,以及其在分布式系统中的应用场景。动态线程池的灵活调整能够有效地提升系统的性能和稳定性,而Nacos作为一个优秀的服务发现和配置中心,为我们提供了强大的支持和便利,帮助我们更好地构建可靠和高效的分布式应用系统。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小工匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值