如何决定 ThreadPoolTaskExecutor 池和队列大小?
How to decide on the ThreadPoolTaskExecutor pools and queue sizes?
这可能是一个更一般的问题,关于如何决定线程池的大小,但让我们在这种情况下使用 Spring ThreadPoolTaskExecutor
。我对池核心和最大大小以及队列容量有以下配置。我已经了解了所有这些配置的含义 - 有一个很好的答案 here.
@SpringBootApplication
@EnableAsync
public class MySpringBootApp {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(MySpringBootApp.class, args);
}
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
return executor;
}
}
以上数字对我来说看起来是随机的,我想了解如何根据我的环境正确设置它们。我将概述我的以下限制条件:
- 应用程序将 运行 在两核 CPU 盒子上
- 执行者将处理一项通常需要 1-2 天的任务
秒完成。
- 通常我希望将 800/分钟的任务提交给我的执行者,以 2500/分钟的速度飙升
- 该任务将构建一些对象并对 Google pubsub 进行 HTTP 调用。
理想情况下,我想了解我需要考虑哪些其他限制,并根据这些限制对我的池和队列大小进行合理的配置。
更新:这个答案多年来获得了一些投票,所以我为那些没有时间阅读我奇怪的比喻的人添加了一个简短的版本:
TL;DR 答案:
实际的约束是(逻辑上的)CPU 核心只能同时 运行 一个线程。因此:
- Number of core : 你的CPUs * 1/(ratio_of_time_your_thread_is_runnable_when_doing_your_task的逻辑核心数)
所以,如果你的机器上有 8 个逻辑核心,你可以安全地将 8 个线程放入你的线程池中(好吧,记得排除其他可能使用的线程)。然后你需要问问自己是否可以放更多:你需要在你的线程池上对你打算 运行 的任务类型进行基准测试:如果你注意到线程平均 运行ning 只有 50%有时,这意味着您的 CPU 有 50% 的时间可以自由地处理另一个线程,并且您可以添加更多线程。
- 队列大小:您可以等待多少个。
队列大小是您的线程池在拒绝之前将接受的项目数。是业务逻辑。这取决于您期望的行为:接受十亿个任务是否有意义?你什么时候扔毛巾?
如果一个任务需要一秒钟才能完成,并且您有 10 个线程,这意味着队列中的第 10,000 个任务有望在 1000 秒内完成。那可以接受吗?
最糟糕的事情是让客户超时并在您完成第一个任务之前重新提交相同的任务。
ELI12 原答案:
这可能不是最准确的答案,但我会尝试:
一个简单的方法是注意您的 2 核 CPU 只能同时在两个线程上工作。
如果您拥有相对较新的 Intel CPU,并且您拥有超线程(又名 HT(TM), HTT(TM), SMT) 开启(通过 BIOS 中的设置),您的操作系统将看到可用核心数为双倍CPU 中的物理核心。
无论哪种方式,从 Java 检测您可以使用多少个核心(或同时不抢占其他线程),只需调用 int cores = Runtime.getRuntime().availableProcessors();
如果您尝试将您的应用程序视为 Workshop(实际的):
- 处理者将由一名雇员代表。它是为产品增加价值的物理单位。
- 一个任务就是一堆原始的material(加上一些指令列表)
- 您的线程是一张桌子,员工可以在上面放置任务和工作。
- 队列大小是将原始 materials 带到办公桌的传送带的长度。
因此,您的问题变为“在员工人数不变的情况下,我如何选择多少张办公桌以及我的传送带在工厂内的长度?”。
对于多少台(线程)部分:
一名员工一次只能在一张办公桌上工作,而且您每张办公桌只能有一名员工。因此,基本设置是至少拥有与员工一样多的办公桌(以避免任何员工(处理者)被排除在外而无法工作。
但是,这取决于您的 activity,您可以为每位员工提供更多办公桌:
如果您的员工需要经常将邮件放入信封,这是一项需要他们全神贯注的操作(在编程中:排序集合、创建对象、递增计数器),拥有更多办公桌无济于事,甚至可能有害,因为您的员工必须
有时换台(切换上下文,这需要一些时间),从而离开他们正在处理的一个,以在另一个上进行工作。
但是,如果您的任务是制作陶器,并且依赖于您的员工等待粘土在烤箱中煮熟(理解 获取对外部资源的访问权限,例如文件系统、Web 服务等等),你的员工可以负担得起在另一张桌子上做模型粘土,然后再回到第一张桌子上。
因此,只要您的任务具有足够大的活动 work/waiting 比率(running/waiting),您就可以为每位员工提供更多办公桌。而办公桌的数量就是您的员工在等待时间内可以完成多少任务。
对于传送带(队列)尺寸部分:
队列大小表示在开始拒绝任何更多任务(通过抛出异常)之前允许排队的项目数,因此是您开始判断的阈值 "ok, I'm already overbooked and won't ever be able to comply"
首先,我会说您的传送带需要安装在车间内。这意味着集合应该足够小以防止内存不足错误(很明显)。
之后,以贵公司政策为准。假设每次客户下订单时都会向传送带添加一个任务(另一个服务调用您的 API)。如果来电者不在乎你花多少时间来遵守并足够信任你执行,那么限制腰带的大小就没有意义了。
但是,如果您可以预料到您的客户在等待他们的陶器一个月后会很生气,并让您同时购买或重新订购另一个陶器,假设第一个订单丢失并且不会费心去检查如果第一个订单完成了......第一个订单是白做的,你不会得到报酬,如果你的客户在你太慢而无法遵守时下了另一个订单,你将进入反馈循环,因为每新订单会减慢整个过程。
因此,在那种情况下,您应该张贴告示告诉您的客户"sorry, we're overbooked, you shouldn't make any new order now, as we won't be able to comply within an acceptable time range"。
那么,队列大小将是:可接受的时间范围 / 完成任务的时间。
具体示例:如果您的客户端服务希望它提交的任务必须在 100 秒内完成,并且知道每个任务需要 1-2 秒,您应该将队列限制为 50-100 个任务因为一旦队列中有 100 个任务在等待,您就可以确定下一个任务不会在 100 秒内完成,因此拒绝该任务以防止服务空等。
这可能是一个更一般的问题,关于如何决定线程池的大小,但让我们在这种情况下使用 Spring ThreadPoolTaskExecutor
。我对池核心和最大大小以及队列容量有以下配置。我已经了解了所有这些配置的含义 - 有一个很好的答案 here.
@SpringBootApplication
@EnableAsync
public class MySpringBootApp {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(MySpringBootApp.class, args);
}
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
return executor;
}
}
以上数字对我来说看起来是随机的,我想了解如何根据我的环境正确设置它们。我将概述我的以下限制条件:
- 应用程序将 运行 在两核 CPU 盒子上
- 执行者将处理一项通常需要 1-2 天的任务 秒完成。
- 通常我希望将 800/分钟的任务提交给我的执行者,以 2500/分钟的速度飙升
- 该任务将构建一些对象并对 Google pubsub 进行 HTTP 调用。
理想情况下,我想了解我需要考虑哪些其他限制,并根据这些限制对我的池和队列大小进行合理的配置。
更新:这个答案多年来获得了一些投票,所以我为那些没有时间阅读我奇怪的比喻的人添加了一个简短的版本:
TL;DR 答案:
实际的约束是(逻辑上的)CPU 核心只能同时 运行 一个线程。因此:
- Number of core : 你的CPUs * 1/(ratio_of_time_your_thread_is_runnable_when_doing_your_task的逻辑核心数)
所以,如果你的机器上有 8 个逻辑核心,你可以安全地将 8 个线程放入你的线程池中(好吧,记得排除其他可能使用的线程)。然后你需要问问自己是否可以放更多:你需要在你的线程池上对你打算 运行 的任务类型进行基准测试:如果你注意到线程平均 运行ning 只有 50%有时,这意味着您的 CPU 有 50% 的时间可以自由地处理另一个线程,并且您可以添加更多线程。
- 队列大小:您可以等待多少个。
队列大小是您的线程池在拒绝之前将接受的项目数。是业务逻辑。这取决于您期望的行为:接受十亿个任务是否有意义?你什么时候扔毛巾? 如果一个任务需要一秒钟才能完成,并且您有 10 个线程,这意味着队列中的第 10,000 个任务有望在 1000 秒内完成。那可以接受吗? 最糟糕的事情是让客户超时并在您完成第一个任务之前重新提交相同的任务。
ELI12 原答案:
这可能不是最准确的答案,但我会尝试:
一个简单的方法是注意您的 2 核 CPU 只能同时在两个线程上工作。
如果您拥有相对较新的 Intel CPU,并且您拥有超线程(又名 HT(TM), HTT(TM), SMT) 开启(通过 BIOS 中的设置),您的操作系统将看到可用核心数为双倍CPU 中的物理核心。
无论哪种方式,从 Java 检测您可以使用多少个核心(或同时不抢占其他线程),只需调用 int cores = Runtime.getRuntime().availableProcessors();
如果您尝试将您的应用程序视为 Workshop(实际的):
- 处理者将由一名雇员代表。它是为产品增加价值的物理单位。
- 一个任务就是一堆原始的material(加上一些指令列表)
- 您的线程是一张桌子,员工可以在上面放置任务和工作。
- 队列大小是将原始 materials 带到办公桌的传送带的长度。
因此,您的问题变为“在员工人数不变的情况下,我如何选择多少张办公桌以及我的传送带在工厂内的长度?”。
对于多少台(线程)部分:
一名员工一次只能在一张办公桌上工作,而且您每张办公桌只能有一名员工。因此,基本设置是至少拥有与员工一样多的办公桌(以避免任何员工(处理者)被排除在外而无法工作。
但是,这取决于您的 activity,您可以为每位员工提供更多办公桌:
如果您的员工需要经常将邮件放入信封,这是一项需要他们全神贯注的操作(在编程中:排序集合、创建对象、递增计数器),拥有更多办公桌无济于事,甚至可能有害,因为您的员工必须 有时换台(切换上下文,这需要一些时间),从而离开他们正在处理的一个,以在另一个上进行工作。
但是,如果您的任务是制作陶器,并且依赖于您的员工等待粘土在烤箱中煮熟(理解 获取对外部资源的访问权限,例如文件系统、Web 服务等等),你的员工可以负担得起在另一张桌子上做模型粘土,然后再回到第一张桌子上。
因此,只要您的任务具有足够大的活动 work/waiting 比率(running/waiting),您就可以为每位员工提供更多办公桌。而办公桌的数量就是您的员工在等待时间内可以完成多少任务。
对于传送带(队列)尺寸部分:
队列大小表示在开始拒绝任何更多任务(通过抛出异常)之前允许排队的项目数,因此是您开始判断的阈值 "ok, I'm already overbooked and won't ever be able to comply"
首先,我会说您的传送带需要安装在车间内。这意味着集合应该足够小以防止内存不足错误(很明显)。
之后,以贵公司政策为准。假设每次客户下订单时都会向传送带添加一个任务(另一个服务调用您的 API)。如果来电者不在乎你花多少时间来遵守并足够信任你执行,那么限制腰带的大小就没有意义了。
但是,如果您可以预料到您的客户在等待他们的陶器一个月后会很生气,并让您同时购买或重新订购另一个陶器,假设第一个订单丢失并且不会费心去检查如果第一个订单完成了......第一个订单是白做的,你不会得到报酬,如果你的客户在你太慢而无法遵守时下了另一个订单,你将进入反馈循环,因为每新订单会减慢整个过程。
因此,在那种情况下,您应该张贴告示告诉您的客户"sorry, we're overbooked, you shouldn't make any new order now, as we won't be able to comply within an acceptable time range"。
那么,队列大小将是:可接受的时间范围 / 完成任务的时间。
具体示例:如果您的客户端服务希望它提交的任务必须在 100 秒内完成,并且知道每个任务需要 1-2 秒,您应该将队列限制为 50-100 个任务因为一旦队列中有 100 个任务在等待,您就可以确定下一个任务不会在 100 秒内完成,因此拒绝该任务以防止服务空等。