Spring:使用ThreadPoolTask​​Executor创建一个真正可扩展的线程池

时间:2016-11-23 22:41:17

标签: java spring multithreading spring-mvc threadpool

我有一个使用Spring ThreadPoolTask​​Executor的REST API。 API在内部将可调用作业提交给ThreadPoolTask​​Executor,后者调用第三方。调用完成后,将执行业务逻辑并将结果返回给调用者。

代码工作正常,但是当负载增加时,性能变得非常糟糕。我怀疑这是ThreadPoolTask​​Executor的线程池大小的结果。所以说如果并发用户是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;
  }

1 个答案:

答案 0 :(得分:0)

我真的认为你拥有的东西看起来很棒。您需要做的就是进行良好的性能测试,以尝试各种队列容量和池大小值。就此而言,任何其他领域。

话虽如此,您可以遵循一些一般性指导原则。例如,如果线程正在进行CPU密集型计算,那么您可能希望将池大小限制为2 *核心数,或者在该附近的某个位置。如果线程正在处理IO密集型任务(例如与外部API进行通信),那么您可以更高。在我过去的自己的测试中,我注意到在双核心英特尔上执行IO密集型任务时,投资回报大约为50个线程。 50后我会停止看到任何性能提升。但请自行测试并验证CPU是否一直在改进。在某些时候,由于上下文切换,添加更多并发线程的成本太高。

考虑复制这些线程将要做的事情,可能使用模拟您正在呼叫的实际API的延迟的远程存根。

另外,请考虑您可能会将下游系统推向突破点。至少有一次,我们编写了非常并行的代码,只是为了发现我们遇到的下游外部API并不是平行的,并且在我们的高峰用法期间导致其他客户中断。我们不小心DOS了API。

请分析您的系统并检查CPU或内存或磁盘IO是否紧张。如果内存,CPU和磁盘IO都没问题,那么可能会遇到下游限制。有一次,我们在一些高度并行的代码中记录太多而磁盘IO是我们的瓶颈。