动态线程池:
使用线程池ThreadPoolExecutor过程中你是否有以下痛点呢?
① 代码中创建了一个ThreadPoolExecutor,但是不知道参数设置多少比较合适。
② 凭经验设置参数值,上线后发现需要调整,改代码重新发布服务,非常麻烦。
③ 线程池相对开发人员来说是个黑盒,运行情况不能及时感知到,直到出现问题。
如果有以上痛点,动态可监控线程 池框架(DynamicTp)或许能帮助到你。如果看过 ThreadPoolExecutor的源码,大概可以知道它对核心参数基本都有提供set/get方法以及一些扩展方法, 可以在运行时动态修改、获取相应 的值,如图所示:
dynamic-tp简介:
官网:
首页 | dynamictp
基于配置中心的轻量级动态线程池,内置监控告警功能,集成常用中间件线程池管理,可通过SPI自定义扩展实现。
为何要引入配置中心?
现在大多数的互联网项目都会微服务化部署,有一套自己的服务治理体系,微服务组件中的分布式配置中心扮演的就是动态修改配置, 实时生效的角色。那么我们是否可以结合 配置中心来做运行时线程池参数的动态调整呢?
答案是肯定的,而且配置中心相对都是高可用的,使用它也不用过于担心配置推送失败这 类问题,而且也能减少研发动态线程池组件本身的难度和工作量。综上,可以总结出以下 的背景:
① 广泛性:在 Java 开发中,想要提高系统性能,线程池已经是一个90%以上开发都 会选择使用的基础工具。
② 不确定性:项目中可能会创建很多线程池,既有IO密集型的,也有CPU密集型的, 但线程池的参数并不好确定;需要有套机制在运行过程中动态去调整参数。
③ 无感知性:线程池运行过程中的各项指标一般感知不到;需要有套监控报警机制在事前、事中就能让开发人员感知到线程池的运行状况及时处理。
④ 高可用性:配置变更需要及时推送到客户端,需要有高可用的配置管理推送服务, 配置中心是现在大多数互联网系统都会使用的组件,与之结合可以极大提高系统可 用性。
功能介绍:
功能 | 描述 |
代码零侵入 | 改变了线程池以往的使用方式,所有配置均放在配置中心,服务启动时会从配置中心 拉取配置生成线程池对象放到Spring容器中,使用时直接从 Spring 容器中获取,对 业务代码零侵入 |
通知告警 | 提供多种报警维度(配置变更通知、活性报警、容量阈值报警、拒绝触发报警、任务 执行或等待超时报警),已支持企业微信、钉钉、飞书、邮件报警,同时提供 SPI 接口可自定义扩展实现 |
运行监控 | 定时采集线程池指标数据,支持通过MicroMeter、JsonLog日志输出、Endpoint三种 方式,可通过SPI接口自定义扩展实现 |
任务增强 | 提供任务包装功能,实现TaskWrapper接口即可,如:MdcTaskWrapper、 TtlTaskWrapper、SwTraceTaskWrapper,可以支持线程池上下文信息传递 |
多配置中心支持 | 基于主流配置中心实现线程池参数动态调整,实时生效,已支持Nacos、Apollo、 Zookeeper、Consul、Etcd、Polaris、ServiceComb,同时也提供SPI接口可自定义扩 展实现 |
轻量简单 | 基于SpringBoot实现,引入starter,接入只需简单4步就可完成,顺利3分钟搞定 |
中间件线程池管理 | 集成管理常用第三方组件的线程池,已集成 Tomcat、Jetty、Undertow、Dubbo、 RocketMq、Hystrix、Grpc、Motan、Okhttp3、Brpc、Tars、SofaRpc、RabbitMq 等组件的线程池管理(调参、监控报警) |
多模式 | 提供了增强线程池 DtpExecutor,IO密集型场景使用的线程池 EagerDtpExecutor, 调度线程池 ScheduledDtpExecutor,有序线程池 OrderedDtpExecutor,可以根据业 务场景选择合适的线程池 |
兼容性 | JUC普通线程池和Spring中的ThreadPoolTaskExecutor也可以被框架管理,@Bean定 义时加 @DynamicTp 注解即可 |
可靠性 | 框架提供的线程池实现了Spring生命周期方法,可以在Spring容器关闭前尽可能多的 处理队列中的任务 |
高可扩展 | 框架核心功能都提供 SPI 接口供用户自定义个性化实现(配置中心、配置文件解析、 通知告警、监控数据采集、任务包装等等) |
线上大规模应用 | 参考《Java线程池实现原理及其在美团业务中的实践》和《动态线程池在转转平台的 实践》,美团及转转内部已经有该理论成熟的应用经验 |
模块划分:
模块名称 | 描述 |
配置变更监听 | ① 监听特定配置中心的指定配置文件(已实现 Nacos、Apollo、Zookeeper、Consul、 Etcd、Polaris、ServiceComb),可通过内部提供的SPI接口扩展其他实现 ② 解析配置文件内容,内置实现 yml、properties、json 配置文件的解析,可通过内 部提供的SPI接口扩展其他实现 ③ 通知线程池管理模块实现参数的刷新 |
线程池管理 | ① 服务启动时从配置中心拉取配置,生成线程池实例注册到内部线程池注册中心以及 Spring容器中 ② 接收配置监听模块的刷新事件,实现线程池参数的刷新 ③ 代码中通过依赖注入(推荐)或者 DtpRegistry.getDtpExecutor() 方法根据线程池 名称来获取线程池实例 |
第三方组件 线程池管理 | ① 利用Spring的事件机制和核心逻辑解耦,服务启动获取第三方中间件的线程池,被 框架管理起来,已集成 Tomcat、Jetty、Undertow、Dubbo、RocketMq、Hystrix、 Grpc、Motan、Okhttp3、Brpc、Tars、SofaRpc、RabbitMq 等组件线程池管理 ② 接受参数刷新、指标收集、通知报警事件,进行相应的处理 |
监控 | 实现监控指标采集以及输出,已支持以下三种方式,也可通过内部提供的SPI接口扩展其 他实现 ① 以JsonLog输出到磁盘指定目录,可以自己采集解析日志,存储展示 ② 以Micrometer采集,引入 Micrometer 相关依赖,暴露相关端点,采集指标数据, 结合Grafana做监控大盘 ③ 暴露自定义Endpoint端点(dynamic-tp),可通过http方式实时访问 |
通知告警 | ① 线程池主要参数变更通知 ② 阻塞队列容量达到设置的告警阈值 ③ 线程池活性达到设置的告警阈值 ④ 触发拒绝策略告警,格式:A/B A:该报警项前后两次报警区间累加数量 B:该报警项累计总数 ⑤ 任务执行超时告警,格式:A/B A:该报警项前后两次报警区间累加数量 B:该报警项累计总数 ⑥ 任务等待超时告警,格式:A/B A:该报警项前后两次报警区间累加数量 B:该报警项累计总 |
代码结构:
package | 描述 |
adapter | 主要是适配第三方组件的线程池管理,目前已经实现的有SpringBoot内置的三大web容器 (Tomcat、Jetty、Undertow)、Dubbo、RocketMq、Hystrix、Grpc、Motan、Okhttp3、 Brpc、Tars、SofaRpc、RabbitMq的线程池管理,后续会接入其他常用组件的线程池管理 |
common | 主要是一些各个模板都会用到的类,解耦依赖,复用代码,大家日常开发中可能也经常会 这样组织代码 |
core | 该框架的核心代码都在这个模块里,包括动态调整参数,监控报警,以及串联整个项目流 程都在此处 |
example | 提供一个简单使用示例,方便使用者参照 |
extension | 放一些扩展功能实现,比如基于redis的流控扩展、邮件发送扩展、skywalking上下文传递 扩展等 |
logging | 用于配置框架内部日志的输出,目前主要用于输出线程池监控指标数据到指定文件 |
starter | 提供独立功能模块的依赖封装、自动配置等相关 |
test | 主要是一些单元测试 |
DynamicTP技术架构图
动态线程池在大厂中的应用:
Java线程池实现原理及其在美团业务中的实践:
Java线程池实现原理及其在美团业务中的实践 - 美团技术团队
动态线程池在转转平台的实践:
动态线程池在转转平台的实践
环境搭建:
启动nacos服务:
进入到nacos\bin目录下:startup.cmd -m standalone
访问nacos:http://localhost:8848/nacos/
初始账号密码均为nacos
启动redis:
安装Node-Exporter
(前提:安装了Docker,Docker安装参考:)