参考美团技术文章:
Java线程池实现原理及其在美团业务中的实践 - 美团技术团队
先看一个公式法
如何合理分配线程池大小的公式法
参考回答:要合理的分配线程池的大小要根据实际情况来定、简单来说的话就是根据CPU密集和IO密集来分配。
1. 核心概念:CPU 密集型 vs. I/O 密集型
-
CPU 密集型: 任务主要消耗 CPU 资源进行计算。例如:复杂的算法、图像处理、视频编码等。
-
I/O 密集型: 任务主要等待 I/O 操作完成。例如:读写磁盘、网络请求、数据库查询等。
2. 线程池大小分配原则
-
目标: 最大化 CPU 利用率、同时避免过多的上下文切换。
-
关键因素: 线程等待时间与 CPU 执行时间的比例。
3. 线程池大小计算公式和方法
-
CPU 密集型:
-
N (CPU 核心数) + 1: 这是常用的建议。
+1
的目的是为了在某个线程因为缺页中断或其他原因阻塞时、CPU 可以立即切换到另一个线程,保持 CPU 的利用率。
-
-
I/O 密集型:
-
2N: 这是一个常用的起点。 由于线程大部分时间都在等待 I/O、因此可以创建更多的线程来利用 CPU 的空闲时间。
-
N * (1 + WT/ST): 这是一个更通用的公式,其中:
-
N
是 CPU 核心数。 -
WT
是线程等待时间(例如等待 I/O 的时间)。 -
ST
是线程运行时间(例如CPU 执行计算的时间)。 -
WT/ST
表示线程等待时间与运行时间的比率。 比率越高、说明线程越偏向 I/O 密集型、可以增加线程数。
-
-
-
通用业务场景 (例如定时推送消息):
-
同样可以使用
N * (1 + WT/ST)
公式。 需要估算或测量线程在等待网络 I/O (例如发送消息) 和执行 CPU 计算 (例如,准备消息内容) 上花费的时间。
-
此时我们可以参考以下公式来计算线程数:
线程数=N(CPU核数)*(1+WT(线程等待时间)/ST(线程时间运行时间))
在不同的业务场景以及不同配置的部署机器中、线程池的线程数量设置是不一样的。其设置不宜过大、也不宜过小、要根据具体情况、计算出一个大概的数值、再通过实际的性能测试、计算出一个合理的线程数量
业界的一些线程池参数配置方案
有没有一种计算公式\能够让开发同学很简易地计算出某种场景中的线程池应该是什么参数呢?
调研了业界的一些线程池参数配置方案:
调研了以上业界方案后\我们并没有得出通用的线程池计算方式。
并发任务的执行情况和任务类型相关、IO密集型和CPU密集型的任务运行起来的情况差异非常大
但这种占比是较难合理预估的\这导致很难有一个简单有效的通用公式帮我们直接计算出结果。
线程池参数动态化
尽管经过谨慎的评估、仍然不能够保证一次计算出来合适的参数、那么我们是否可以将修改线程池参数的成本降下来、这样至少可以发生故障的时候可以快速调整从而缩短故障恢复的时间呢?
基于这个思考、我们是否可以将线程池的参数从代码中迁移到分布式配置中心上、实现线程池参数可动态配置和即时生效、线程池参数动态化前后的参数修改流程对比如下:
基于以上三个方向对比、我们可以看出参数动态化方向简单有效
配高配低都不合理 要知道配高配低的影响是什么
美团的动态化线程池方案:
由于难以一次性配置出合适的参数、美团采用了动态化线程池的方案、核心思想是:
- 简化配置: 只暴露
corePoolSize
、maximumPoolSize
和workQueue
(提供两种队列选择) 这三个关键参数。 - 参数动态修改: 将线程池配置放在分布式配置中心、允许动态修改参数并立即生效。 利用
ThreadPoolExecutor
提供的setCorePoolSize
、setMaximumPoolSize
等 setter 方法。 - 增加线程池监控: 监控线程池的运行状态,包括活跃度、任务执行情况、Reject 异常等。
监控和告警:
- 负载监控: 通过 活跃度 = activeCount / maximumPoolSize来衡量线程池的负载。
- 告警: 当发生 Reject 异常或队列中有等待任务 (超过阈值) 时、触发告警。
- 任务级精细化监控: 对不同业务任务进行 Transaction 打点、了解每种任务的执行频率和耗时。
- 运行时状态实时查看: 查看线程池的实时状态、如工作线程数、已执行任务数、队列中等待的任务数等。
总结:
设置线程池参数是一个迭代的过程、需要根据实际运行情况进行调整。 没有一劳永逸的配置。 美团的动态化线程池方案提供了一种更灵活的方式来管理线程池、通过动态调整参数和监控线程池状态、可以更好地适应业务需求的变化。 核心在于监控、没有监控、一切优化都是空谈。
设置线程池参数的步骤可以概括为:
- 理解核心参数和队列类型。
- 确定任务类型 (CPU 密集型或 IO 密集型)。
- 根据任务类型和公式估算初始参数。
- 选择合适的阻塞队列。
- 部署并监控线程池的运行状态 (活跃度、队列长度、Reject 异常等)。
- 根据监控数据动态调整参数。
- 设置告警阈值,及时发现问题。
美团的实践表明、动态化线程池是一种有效的解决方案、可以降低线程池参数修改的成本、并提高系统的稳定性和性能