Web应用扩展系列(2):如何确定Web应用线程池的大小

继续上篇博文《Web应用扩展系列(1):架构篇》,本文中将会介绍一个Web应用扩展过程中的常见问题,即如何设定web应用线程池的大小,这经常在部署生产环境以及测试web应用性能的时候遇到。

线程池

对于Web应用而言,线程池的大小决定了同一时刻能够被处理的并发(Concurrent)请求的个数。如果一个Web应用在运行时收到了多于线程池大小的请求,那么多出来的请求要么被丢弃,要么会被排队。

请注意并发(concurrent)与并行(parallel)的区别。并发请求指的是通过分享CPU时间片而不是独占CPU而执行的请求;而并行指的是通过在多个处理器上同时执行的请求。

在一些非阻塞IO的应用例如NodeJS之中,一个单线程(或进程)能够并发处理多个请求。而在多核CPU的系统中,并行请求能够通过增加线程或进程来进行处理。

在阻塞IO应用例如Java SpringMVC框架中,一个单线程仅仅能够并发处理一个请求,而为了并发处理多个请求,我们必须增加处理线程的数量。

CPU密集型应用

对于CPU密集型应用而言,线程池的大小应该等于系统中cpu的数量。更多的线程会因为线程的上下文切换而中断当前请求的执行并且增加响应时间。

非阻塞IO应用因为当请求被处理时没有线程的等待时间,所以此时将会变成CPU密集型应用。

IO密集型应用

对于IO密集型应用而言,确定线程池的大小相对比较复杂,因为这涉及到下游系统的响应时间,因为一个线程常常因为等待其他系统的响应而被阻塞。所以我们必须增加线程的数量以更好地利用CPU,正如这篇文章Reactor Pattern Part 1 : Applications with Blocking I/O所提到的。

利特尔法则(Little’s law)

利特尔法则(Little’s law)被广泛运用于许多非技术领域,例如银行利用此法则确定服务客户的银行柜台柜员的数量。

利特尔法则:在一个稳定的系统中,长时间观察到的平均顾客数量L,等于,长时间观察到的有效到达速率λ与平均每个顾客在系统中花费的时间之乘积,即L = λW。

利特尔法则在web app中的应用:一个系统的平均服务线程数量(Threads),等于平均请求到达率(WebRequests per sec)乘以平均服务时间(ResponseTime)。

  • Threads = 服务线程数量
  • WebRequests per sec = 能够在一秒钟内被处理的web请求数量
  • ResponseTime = 处理一个web请求所需的时间

虽然上面的等式指出了处理访问请求所需线程的数量,然而它并未说明线程与CPU个数的关系,即在一个拥有x个CPU的系统上为线程池分配多少CPU资源更为合理。

确定线程池大小的测试

确定线程池大小的过程就是在吞吐率以及响应时间之间寻找平衡点。首先从一个CPU一个线程开始(线程池大小=CPU个数),此时应用的线程池大小将会与下游系统的平均响应时间成反比,直到CPU利用率最大化或响应时间不再减少。

下面的几个图表展示了请求数量、CPU和响应时间之间的相互关系。

关于CPU和请求个数之间关系的图展示了当Web应用负载增加时CPU利用率的变化情况。

响应时间和请求数量关系图展示了增加应用负载所引起的响应时间变化。

绿点展示了理想状况下的吞吐量和响应时间。

线程池大小 = CPU个数

上图描述了当线程数量等于CPU数量IO密集型应用的相关情况。线程在等待下游系统响应的时候陷入了阻塞的状态。响应时间随着请求开始排队而不断增加,这是因为线程正在被阻塞。当所有的线程都被阻塞及时cpu利用率此时还很低,但应用已经开始拒绝服务。

增大线程池

 

上图展示的是当增大线程池的时候IO密集型应用会发生什么。因为有了更多的线程,所以相关的上下文切换将会变得十分频繁。而cpu利用率即使在吞吐量没有明显增加的情况下也会飙升,这是因为多了许多不必要的线程切换开销。响应时间也会增加因为请求因为上下文切换而被打断了。

理想的线程池大小

 

上图展示了对于IO密集型应用而言最佳的线程池大小。CPU能够得到很有效的利用,吞吐量很客观并且上下文切换很少。我们可以看到最佳响应时间在有效的请求处理伴随着很少的上下文切换的情况下出现。

线程池隔离

对于大部分Web应用而言,很少有请求会花费比别的请求长得多的时间来处理。最慢的请求也许会占用所有的线程资源并且可能会搞挂整个应用。

对于这个问题有以下一些解决方法:

  • 对于相对而言慢的请求用隔离的系统(应用)来处理
  • 在同一个应用中,分配一个隔离的线程池来处理慢请求

如何确定一个IO密集型Web应用的最佳线程池大小是一个十分困难的工作,通常需要很多次的实验才能得出结论。当然了,在应用中增加更多的线程池无疑会增加这个过程的难度。

收藏 评论

关于作者:熊崽Kevin

爱编程,爱画画,爱生活。云计算与大数据应用开发工程师。Imagine the fire, waiting for rises. BLOG(新浪微博:@熊崽Kevin) 个人主页 · 我的文章 · 17

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部