我有一个使用Spring ThreadPoolTaskExecutor的REST API。 API在内部将可调用作业提交给ThreadPoolTaskExecutor,后者调用第三方。调用完成后,将执行业务逻辑并将结果返回给调用者。
代码工作正常,但是当负载增加时,性能变得非常糟糕。我怀疑这是ThreadPoolTaskExecutor的线程池大小的结果。所以说如果并发用户是n但我们只有x个线程(x小于n)那么x线程将不必要等待有限数量的线程来处理他们的请求。
我想并行处理第三方调用,但不想创建一个包含大量线程的线程池。
我的选项是使用Executors.newFixedThreadPool(y)。在方法内使用它,一旦完成该过程,就关闭该对象。这是可能的,但不确定它的副作用,如果从方法创建固定线程池是一个好习惯。
其他选项可能是使用某种对象池,如GenericObjectPoolConfig,并使用它来获取线程。
其他选项可能是将最大池大小设置为Integer.max并将队列容量减少到1.因此,每次发出新请求而不是将对象存储在队列中时,它都会创建一个新线程。
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(20);
threadPoolTaskExecutor.setMaxPoolSize(Integer.MAX_VALUE);
threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
threadPoolTaskExecutor.setQueueCapacity(1);
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
threadPoolTaskExecutor.initialize();
如果有人可以分享他的想法,将会很有帮助。
@Configuration
public class TestConfiguration{
@Bean
public ConcurrentTaskExecutor concurrentTaskExecutor() {
ConcurrentTaskExecutor concurrentTaskExecutor = new ConcurrentTaskExecutor();
concurrentTaskExecutor.setConcurrentExecutor(getExecutor());
return concurrentTaskExecutor;
}
private Executor getExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(20);
threadPoolTaskExecutor.setMaxPoolSize(30);
threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
threadPoolTaskExecutor.setQueueCapacity(75);
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
}
@Service
public class TestServiceImpl{
@Autowired
private ConcurrentTaskExecutor concurrentTaskExecutor;
@Override
@Transactional
public DTO getDTO() {
Callable<TESTDTO> test1Callable = new Test1Callable();
Future<TESTDTO> testDTO1 = concurrentTaskExecutor.submit(test1Callable);
Callable<TESTDTO> test2Callable = new Test2Callable();
Future<TESTDTO> testDTO2 =concurrentTaskExecutor.submit(test2Callable);
Callable<TESTDTO> test3Callable = new Test3Callable();
Future<TESTDTO> testDTO3 =concurrentTaskExecutor.submit(test3Callable);
// Perform logic on DTO's
return DTO;
}
答案 0 :(得分:0)
我真的认为你拥有的东西看起来很棒。您需要做的就是进行良好的性能测试,以尝试各种队列容量和池大小值。就此而言,任何其他领域。
话虽如此,您可以遵循一些一般性指导原则。例如,如果线程正在进行CPU密集型计算,那么您可能希望将池大小限制为2 *核心数,或者在该附近的某个位置。如果线程正在处理IO密集型任务(例如与外部API进行通信),那么您可以更高。在我过去的自己的测试中,我注意到在双核心英特尔上执行IO密集型任务时,投资回报大约为50个线程。 50后我会停止看到任何性能提升。但请自行测试并验证CPU是否一直在改进。在某些时候,由于上下文切换,添加更多并发线程的成本太高。
考虑复制这些线程将要做的事情,可能使用模拟您正在呼叫的实际API的延迟的远程存根。
另外,请考虑您可能会将下游系统推向突破点。至少有一次,我们编写了非常并行的代码,只是为了发现我们遇到的下游外部API并不是平行的,并且在我们的高峰用法期间导致其他客户中断。我们不小心DOS了API。
请分析您的系统并检查CPU或内存或磁盘IO是否紧张。如果内存,CPU和磁盘IO都没问题,那么可能会遇到下游限制。有一次,我们在一些高度并行的代码中记录太多而磁盘IO是我们的瓶颈。