文章目录
- 引言
- 概述
- 什么是动态线程池
- Nacos简介
- 如何利用Nacos实现动态线程池管理
- 应用场景
- Code
- 版本说明
- POM
- 配置文件
- Nacos Config配置文件加载顺序
- 1. bootstrap.yml的加载
- 2. application.yml的加载
- 注意事项
- 示例
- nacos配置
- Data Id
- Nacos中Data ID的命名格式解释
- `${spring.application.name}`
- `${spring.profile.active}`
- `${spring.cloud.nacos.config.file-extension}`
- 合并后的Data ID
- 示例
- Configuration & Change Callback
- 测试
- 小结
引言
Java线程池实现原理及其在美团业务中的实践
开源组件:Hippo4J 、 dynamic-tp
概述
随着现代应用程序的复杂性不断增加,动态线程池管理成为了构建可靠和高效系统的关键之一。而Nacos作为一个优秀的服务发现和配置中心,能够帮助我们实现动态线程池的灵活管理。
我们这里将介绍如何利用Nacos来实现高效的动态线程池管理,并探讨其在分布式系统中的应用。
什么是动态线程池
在软件开发中,线程池是一种管理和重用线程的机制,它能够有效地控制并发执行的线程数量,避免资源耗尽和性能下降。动态线程池则是在传统线程池的基础上,能够根据系统负载和资源需求动态调整线程数量的一种改进型线程池管理方式。
Nacos简介
Nacos是阿里巴巴开源的一款多功能的服务发现和配置中心,它提供了服务注册与发现、动态配置管理、服务路由等功能,被广泛应用于微服务架构中。其优势在于高可用、易扩展、支持多种注册中心和配置源等特性。
如何利用Nacos实现动态线程池管理
-
服务注册与发现:首先,将应用程序注册到Nacos服务注册中心,使其成为Nacos的服务实例。这样,我们就可以利用Nacos的服务发现功能来获取可用的服务实例列表。
-
动态配置管理:借助Nacos的动态配置管理功能,我们可以将线程池的配置信息存储在Nacos的配置中心中,例如线程池大小、核心线程数、最大线程数、线程存活时间等参数。
-
监听配置变更:通过监听Nacos配置中心的变更通知,当线程池配置发生改变时,我们可以及时感知并动态调整线程池的参数,从而实现线程池的动态管理。
-
实时监控与调整:结合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.properties
或application.yml
中通过spring.application.name
属性进行配置的。这个属性通常用来标识应用的名称,用于服务注册、日志输出等场景。
${spring.profile.active}
${spring.profile.active}
表示Spring Boot应用当前的Profile,它是通过spring.profiles.active
属性进行配置的。Profile用于区分不同环境下的配置信息,例如dev
、test
、prod
等,可以根据实际情况进行配置。
${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("order-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.size
和max.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作为一个优秀的服务发现和配置中心,为我们提供了强大的支持和便利,帮助我们更好地构建可靠和高效的分布式应用系统。