文章目录
- 🔊博主介绍
- 🥤本文内容
- 部署
- 运行模式
- 集成
- 线程池监控配置
- 参数默认配置
- 📢文章总结
- 📥博主目标
🔊博主介绍
🌟我是廖志伟,一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家博主、阿里云专家博主、清华大学出版社签约作者、产品软文专业写手、技术文章评审老师、问卷调查设计师、个人社区创始人、开源项目贡献者。🌎跑过十五公里、🚀徒步爬过衡山、🔥有过三个月减肥20斤的经历、是个喜欢躺平的狠人。
📕拥有多年一线研发和团队管理经验,研究过主流框架的底层源码(Spring、SpringBoot、Spring MVC、SpringCould、Mybatis、Dubbo、Zookeeper),消息中间件底层架构原理(RabbitMQ、RockerMQ、Kafka)、Redis缓存、MySQL关系型数据库、 ElasticSearch全文搜索、MongoDB非关系型数据库、Apache ShardingSphere分库分表读写分离、设计模式、领域驱动DDD、Kubernetes容器编排等。
📙有过从0到1的项目高并发项目开发与管理经验,对JVM调优、MySQL调优、Redis调优 、ElasticSearch调优、消息中间件调优、系统架构调优都有着比较全面的实战经验。
📘有过云端搭建服务器环境,自动化部署CI/CD,弹性伸缩扩容服务器(最高200台),了解过秒级部署(阿里云的ACK和华为云的云容器引擎CCE)流程,能独立开发和部署整个后端服务,有过分库分表的实战经验。
🎥经过多年在CSDN创作上千篇文章的经验积累,我已经拥有了不错的写作技巧,与清华大学出版社签下了四本书籍的合约,并将陆续在明年出版。这些书籍包括了基础篇、进阶篇、架构篇的📌《Java项目实战—深入理解大型互联网企业通用技术》📌,以及📚《解密程序员的思维密码–沟通、演讲、思考的实践》📚。具体出版计划会根据实际情况进行调整,希望各位读者朋友能够多多支持!
文章目录
- 🔊博主介绍
- 🥤本文内容
- 部署
- 运行模式
- 集成
- 线程池监控配置
- 参数默认配置
- 📢文章总结
- 📥博主目标
🌾阅读前,快速浏览目录和章节概览可帮助了解文章结构、内容和作者的重点。了解自己希望从中获得什么样的知识或经验是非常重要的。建议在阅读时做笔记、思考问题、自我提问,以加深理解和吸收知识。
💡在这个美好的时刻,本人不再啰嗦废话,现在毫不拖延地进入文章所要讨论的主题。接下来,我将为大家呈现正文内容。
🥤本文内容
部署
使用 Docker 运行服务端,默认使用内置 H2 数据库,数据持久化到 Docker 容器存储卷中。
docker run -d -p 6691:6691 --name hippo4j-server \
-e DATASOURCE_MODE=mysql \
-e DATASOURCE_HOST=192.168.3.200 \
-e DATASOURCE_PORT=3306 \
-e DATASOURCE_DB=hippo4j_manager \
-e DATASOURCE_USERNAME=root \
-e DATASOURCE_PASSWORD=root \
hippo4j/hippo4j-server
访问 Server 控制台,路径 http://hadoop3:6691/index.html,默认用户名密码:admin / 123456
运行模式
Hippo4j 分为两种使用模式:轻量级依赖配置中心以及无中间件依赖版本。
Hippo4jConfig系统:它提供了极具可行性且灵活的动态线程池管理方案。利用了Nacos、Apollo、Zookeeper、ETCD、Polaris和Consul等多元化的第三方配置中心这些特性,可根据用户需求完成线程池参数的实时调整与更新,从而实现高度智能化的线程池管理功能。此外,该系统还具备实时预警及监控等独特功能,使得整个系统的稳定性得以提高并大幅提升表现效果。
Hippo4jServer服务器:在此基础上进行扩展,并采取了无需借助任何中介软件的全新发布方式,大大降低了系统依赖成本。其主要工作原理是由用户通过直观易懂的Web界面去直接操作线程池的建立、变更以及状态的浏览等任务,由此最大限度地简化了操作流程,减轻了用户负担。虽然Hippo4jServer在各项功能上都远超过Hippo4jConfig,但是在保持稳定性的同时,复杂度也相应有所增加。这就需要在服务器端额外安装一款Java程序以便运行,并且还依赖MySQL数据库作为数据存储设施。
集成
<dependency>
<groupId>cn.hippo4j</groupId>
<artifactId>hippo4j-config-spring-boot-starter</artifactId>
<version>1.5.0</version>
</dependency>
请在应用程序启动类中引入注解@EnablesDynamicThreadPool以实现动态线程池功能。
@SpringBootApplication
@EnableDynamicThreadPool
public class ExampleApplication {
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
}
请参照示例项目hippo4j-config-nacos-spring-boot-starter-example进行配置调整,具体步骤包括:
1)设定Nacos服务的URL;
2)在Nacos所关联的空间和分组内创建名为hippo4j-nacos.properties的配置文件;
3)将原先在bootstrap.properties文件中的相关配置参数迁移至上述新创配置文件之中。
spring.dynamic.thread-pool.enable=true # 启用动态线程池
spring.dynamic.thread-pool.banner=true # 启用线程池横幅
spring.dynamic.thread-pool.check-state-interval=5 # 线程池状态检查间隔(秒)
spring.dynamic.thread-pool.monitor.enable=true # 启用线程池监控
spring.dynamic.thread-pool.monitor.collect-types=micrometer # 监控数据收集类型
spring.dynamic.thread-pool.monitor.thread-pool-types=dynamic,web # 监控的线程池类型
spring.dynamic.thread-pool.monitor.initial-delay=10000 # 监控延迟启动时间(毫秒)
spring.dynamic.thread-pool.monitor.collect-interval=5000 # 监控数据采集间隔(毫秒)
spring.dynamic.thread-pool.notify-platforms[0].platform=WECHAT # 通知平台:微信
spring.dynamic.thread-pool.notify-platforms[0].token=ac0426a5-c712-474c-9bff-72b8b8f5caff # 微信通知的令牌
spring.dynamic.thread-pool.notify-platforms[1].platform=DING # 通知平台:钉钉
spring.dynamic.thread-pool.notify-platforms[1].token=56417ebba6a27ca352f0de77a2ae9da66d01f39610b5ee8a6033c60ef9071c55 # 钉钉通知的令牌
spring.dynamic.thread-pool.notify-platforms[2].platform=LARK # 通知平台:飞书
spring.dynamic.thread-pool.notify-platforms[2].token=2cbf2808-3839-4c26-a04d-fd201dd51f9e # 飞书通知的令牌
spring.dynamic.thread-pool.executors[0].thread-pool-id=message-consume # 线程池ID:消息消费
spring.dynamic.thread-pool.executors[0].thread-name-prefix=message-consume # 线程名前缀:消息消费
spring.dynamic.thread-pool.executors[0].core-pool-size=4 # 核心线程数
spring.dynamic.thread-pool.executors[0].maximum-pool-size=6 # 最大线程数
spring.dynamic.thread-pool.executors[0].queue-capacity=512 # 队列容量
spring.dynamic.thread-pool.executors[0].blocking-queue=ResizableCapacityLinkedBlockingQueue # 阻塞队列类型
spring.dynamic.thread-pool.executors[0].execute-time-out=800 # 任务执行超时时间(毫秒)
spring.dynamic.thread-pool.executors[0].rejected-handler=AbortPolicy # 拒绝策略
spring.dynamic.thread-pool.executors[0].keep-alive-time=6691 # 空闲线程存活时间(毫秒)
spring.dynamic.thread-pool.executors[0].allow-core-thread-time-out=true # 是否允许核心线程超时销毁
spring.dynamic.thread-pool.executors[0].alarm=true # 是否启用告警
spring.dynamic.thread-pool.executors[0].active-alarm=80 # 活跃线程告警阈值(百分比)
spring.dynamic.thread-pool.executors[0].capacity-alarm=80 # 队列容量告警阈值(百分比)
spring.dynamic.thread-pool.executors[0].notify.interval=8 # 告警通知间隔(秒)
spring.dynamic.thread-pool.executors[0].notify.receives=chen.ma # 告警通知接收人
spring.dynamic.thread-pool.executors[1].thread-pool-id=message-produce # 线程池ID:消息生产
spring.dynamic.thread-pool.executors[1].thread-name-prefix=message-produce # 线程名前缀:消息生产
spring.dynamic.thread-pool.executors[1].core-pool-size=2 # 核心线程数
spring.dynamic.thread-pool.executors[1].maximum-pool-size=4 # 最大线程数
spring.dynamic.thread-pool.executors[1].queue-capacity=1024 # 队列容量
spring.dynamic.thread-pool.executors[1].blocking-queue=ResizableCapacityLinkedBlockingQueue # 阻塞队列类型
spring.dynamic.thread-pool.executors[1].execute-time-out=800 # 任务执行超时时间(毫秒)
spring.dynamic.thread-pool.executors[1].rejected-handler=AbortPolicy # 拒绝策略
spring.dynamic.thread-pool.executors[1].keep-alive-time=6691 # 空闲线程存活时间(毫秒)
spring.dynamic.thread-pool.executors[1].allow-core-thread-time-out=true # 是否允许核心线程超时销毁
spring.dynamic.thread-pool.executors[1].alarm=true # 是否启用告警
spring.dynamic.thread-pool.executors[1].active-alarm=80 # 活跃线程告警阈值(百分比)
spring.dynamic.thread-pool.executors[1].capacity-alarm=80 # 队列容量告警阈值(百分比)
spring.dynamic.thread-pool.executors[1].notify.interval=8 # 告警通知间隔(秒)
spring.dynamic.thread-pool.executors[1].notify.receives=chen.ma # 告警通知接收人
如果是yaml文件,SpringBoot 应用配置文件添加::
server:
port: 8090
servlet:
context-path: /example
spring:
profiles:
active: dev
dynamic:
thread-pool:
# 是否开启动态线程池
enable: true
# 是否打印 banner
banner: true
# 是否开启线程池数据采集,对接 Micrometer、ES、Log 等
collect: true
# 检查线程池状态,是否达到报警条件,单位毫秒
check-state-interval: 3000
# 通知报警平台,请替换为自己创建的群机器人
notify-platforms:
- platform: 'WECHAT'
token: xxx
- platform: 'DING'
token: xxx
secret: xxx # 加签专属
- platform: 'LARK'
token: xxx
# Nacos、Apollo、Zookeeper、ETCD、Polaris、Consul 任选其一
nacos:
data-id: xxx
group: xxx
apollo:
namespace: xxxx
# 配置中心文件格式
config-file-type: yml
# 支持 tomcat、undertow、jetty 三种容器线程池
web:
core-pool-size: 100
maximum-pool-size: 200
keep-alive-time: 1000
# 全局通知配置-是否报警
alarm: true
# 活跃度报警阈值;假设线程池最大线程数 10,当线程数达到 8 发起报警
active-alarm: 80
# 容量报警阈值;假设阻塞队列容量 100,当容量达到 80 发起报警
capacity-alarm: 80
# 报警间隔,同一线程池下同一报警纬度,在 interval 时间内只会报警一次,单位秒
alarm-interval: 8
# 企业微信填写用户 ID(填写其它将无法达到 @ 效果)、钉钉填手机号、飞书填 ou_ 开头唯一 ID
receives: xxx
# 动态线程池列表
executors:
- thread-pool-id: 'message-consume'
# 核心线程数
core-pool-size: 1
# 最大线程数
maximum-pool-size: 1
# 阻塞队列名称,参考 BlockingQueueTypeEnum,支持 SPI
blocking-queue: 'LinkedBlockingQueue'
# 阻塞队列大小
queue-capacity: 1
# 执行超时时间,超过此时间发起报警,单位毫秒
execute-time-out: 1000
# 拒绝策略名称,参考 RejectedPolicyTypeEnum,支持 SPI
rejected-handler: 'AbortPolicy'
# 线程存活时间,单位秒
keep-alive-time: 1024
# 是否允许核心线程超时
allow-core-thread-time-out: true
# 线程工厂名称前缀
thread-name-prefix: 'message-consume'
# 是否报警
alarm: true
# 活跃度报警阈值;假设线程池最大线程数 10,当线程数达到 8 发起报警
active-alarm: 80
# 容量报警阈值;假设阻塞队列容量 100,当容量达到 80 发起报警
capacity-alarm: 80
# 通知配置,线程池中通知配置如果存在,则会覆盖全局通知配置
notify:
# 报警间隔,同一线程池下同一报警纬度,在 interval 时间内只会报警一次,单位分钟
interval: 8
# 企业微信填写用户 ID(填写其它将无法达到 @ 效果)、钉钉填手机号、飞书填 ou_ 开头唯一 ID
receives: xxx
- thread-pool-id: 'message-produce'
core-pool-size: 1
maximum-pool-size: 1
queue-capacity: 1
execute-time-out: 1000
blocking-queue: 'LinkedBlockingQueue'
rejected-handler: 'AbortPolicy'
keep-alive-time: 1024
allow-core-thread-time-out: true
thread-name-prefix: 'message-consume'
alarm: true
active-alarm: 80
capacity-alarm: 80
notify:
interval: 8
receives: xxx
在启动Hippo4j-Config-Nacos-Spring-Boot-Starter-Example工程的 ConfigNacosExampleApplication时,我们需要对Nacos配置中的hippo4j-nacos.properties文件进行一定的修改操作。如此一来,将在控制台上观察到日志输出中包含了有关线程池信息的变动信息。
根据ThreadPoolExecutor的特性,我们必须增加一个新的线程池配置类,并同时使用@DynamicThreadPool注解加以标识。其中,“threadPoolId"字段代表了服务器端所创建的线程池ID。这也是先前在配置文件中设定的参数值,即"spring.dynamic.thread-pool.executors[0].thread-pool-id=message-consume”。
个性化定制方面,Hippo4j-Config主要依赖于配置中心实现线程池配置的动态更改。然而,现阶段在这种模式下存在着一些不足之处,例如:当配置文件发生改变时,会导致所有客户端为了适应变更而重新加载数据。因此,我们希望Hippo4j-Config能够具备如同Hippo4j-Server那样,支持客户端集群个性化配置的能力,以便我们能针对单一客户端进行独立的配置变更调整。
ThreadPoolExecutor 适配
添加线程池配置类,通过 @DynamicThreadPool 注解修饰。threadPoolId 为服务端创建的线程池 ID。
import cn.hippo4j.core.executor.DynamicThreadPool;
import cn.hippo4j.core.executor.support.ThreadPoolBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class ThreadPoolConfig {
@Bean
@DynamicThreadPool
public ThreadPoolExecutor messageConsumeDynamicExecutor() {
String threadPoolId = "message-consume";
ThreadPoolExecutor messageConsumeDynamicExecutor = ThreadPoolBuilder.builder()
.threadFactory(threadPoolId)
.threadPoolId(threadPoolId)
.dynamicPool()
.build();
return messageConsumeDynamicExecutor;
}
@Bean
@DynamicThreadPool
public ThreadPoolExecutor messageProduceDynamicExecutor() {
String threadPoolId = "message-produce";
ThreadPoolExecutor messageProduceDynamicExecutor = ThreadPoolBuilder.builder()
.threadFactory(threadPoolId)
.threadPoolId(threadPoolId)
.dynamicPool()
.build();
return messageProduceDynamicExecutor;
}
}
通过 ThreadPoolBuilder 构建动态线程池,只有 threadFactory、threadPoolId 为必填项,其它参数会从配置中心拉取。
项目中使用上述定义的动态线程池,如下所示:
@Resource
private ThreadPoolExecutor messageConsumeDynamicExecutor;
messageConsumeDynamicExecutor.execute(() -> xxx);
@Resource
private ThreadPoolExecutor messageProduceDynamicExecutor;
messageProduceDynamicExecutor.execute(() -> xxx);
线程池监控配置
监控前置条件:需要先完成 hippo4j-config 的 接入工作。
接下来引入 SpringBoot Actuator。Spring 2.x 一般都有版本指定,所以这里不用写版本号。
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
添加动态线程池监控相关配置:
management:
metrics:
export:
prometheus:
enabled: true
server:
port: 29999 # 可选配置,如果不配置该 port,直接使用 ${server.port}
endpoints:
web:
exposure:
include: '*' # 测试使用,开启了所有端点,生产环境不建议 *
spring:
dynamic:
thread-pool:
monitor:
enable: true # 是否开启采集线程池运行时数据
collect-interval: 5000 # 采集线程池运行数据频率
collect-types: micrometer # 采集线程池运行数据的类型。eg:log、micrometer。多个可以同时使用,默认 micrometer
initial-delay: 10000 # 项目启动后延迟多久进行采集
thread-pool-types: dynamic # 采集线程池的类型。eg:dynamic、web、adapter。可任意配置,默认 dynamic
项目启动,访问 http://localhost:29999/actuator/prometheus 出现 dynamic_thread_pool_ 前缀的指标,即为成功。
配置 Prometheus
通过 Docker 启动 Prometheus 服务。
docker run -d -p 9090:9090 --name prometheus prom/prometheus
添加 Prometheus 抽取数据任务。
进入 prometheus 容器内部,编辑 prometheus 配置文件
docker exec -it prometheus /bin/sh
vi /etc/prometheus/prometheus.yml
scrape_configs 节点下新添加一个 job,如果 Prometheus 是 Docker 方式部署,{scrape_configs.static_configs.targets} 需要写本机的 IP。
scrape_configs:
- job_name: 'dynamic-thread-pool-job'
scrape_interval: 5s
metrics_path: '/actuator/prometheus'
static_configs:
- targets: [ '127.0.0.1:29999' ]
配置成功后 exit 退出容器,并进行 Prometheus 容器重启 docker restart prometheus。
访问 Prometheus 控制台 http://localhost:9090/graph 路径,能够展示相关指标即为配置成功。
配置 Grafana
docker run -d -p 3000:3000 --name=grafana grafana/grafana
访问 Grafana 地址,http://localhost:3000 用户名密码:admin
Grafana 访问 http://localhost:3000/datasources 导入 Prometheus 数据源。
如果 Prometheus 为 Docker 方式部署,HTTP URL 需要为本地 IP,比如:http://192.168.1.5:9090
参数默认配置
项目中线程池一多,配置文件中配置就显得很臃肿。为此 hippo4j-config 开发出了动态线程池默认配置。
spring:
dynamic:
thread-pool:
default-executor:
core-pool-size: 4
maximum-pool-size: 6
blocking-queue: ResizableCapacityLinkedBlockingQueue
queue-capacity: 1024
execute-time-out: 1000
keep-alive-time: 9999
rejected-handler: AbortPolicy
active-alarm: 90
capacity-alarm: 85
alarm: true
allow-core-thread-time-out: true
notify:
interval: 5
receives: chen.ma
executors:
- thread-pool-id: message-produce
- thread-pool-id: message-consume
core-pool-size: 80
maximum-pool-size: 100
execute-time-out: 1000
notify:
interval: 6
receives: chen.ma
📢文章总结
对本篇文章进行总结:
🔔以上就是今天要讲的内容,阅读结束后,反思和总结所学内容,并尝试应用到现实中,有助于深化理解和应用知识。与朋友或同事分享所读内容,讨论细节并获得反馈,也有助于加深对知识的理解和吸收。
🔔如果您需要转载或者搬运这篇文章的话,非常欢迎您私信我哦~
🚀🎉希望各位读者大大多多支持用心写文章的博主,现在时代变了,🚀🎉 信息爆炸,酒香也怕巷子深🔥,博主真的需要大家的帮助才能在这片海洋中继续发光发热🎨,所以,🏃💨赶紧动动你的小手,点波关注❤️,点波赞👍,点波收藏⭐,甚至点波评论✍️,都是对博主最好的支持和鼓励!
- 💂 博客主页: 我是廖志伟
- 👉开源项目:java_wxid
- 🌥 哔哩哔哩:我是廖志伟
- 🎏个人社区:幕后大佬
- 🔖个人微信号:
SeniorRD
- 🎉微信号二维码:
📥博主目标
- 🍋程序开发这条路不能停,停下来容易被淘汰掉,吃不了自律的苦,就要受平庸的罪,持续的能力才能带来持续的自信。我本是一个很普通的程序员,放在人堆里,除了与生俱来的盛世美颜,就剩180的大高个了,就是我这样的一个人,默默写博文也有好多年了。
- 📺有句老话说的好,牛逼之前都是傻逼式的坚持,希望自己可以通过大量的作品、时间的积累、个人魅力、运气、时机,可以打造属于自己的技术影响力。
- 💥内心起伏不定,我时而激动,时而沉思。我希望自己能成为一个综合性人才,具备技术、业务和管理方面的精湛技能。我想成为产品架构路线的总设计师,团队的指挥者,技术团队的中流砥柱,企业战略和资本规划的实战专家。
- 🎉这个目标的实现需要不懈的努力和持续的成长,但我必须努力追求。因为我知道,只有成为这样的人才,我才能在职业生涯中不断前进并为企业的发展带来真正的价值。在这个不断变化的时代,我们必须随时准备好迎接挑战,不断学习和探索新的领域,才能不断地向前推进。我坚信,只要我不断努力,我一定会达到自己的目标。
🔔有需要对自己进行综合性评估,进行职业方向规划,我可以让技术大牛帮你模拟面试、针对性的指导、传授面试技巧、简历优化、进行技术问题答疑等服务。
可访问:https://java_wxid.gitee.io/tojson/