GVKun编程网logo

如何决定Web应用线程池大小(如何决定web应用线程池大小)

10

在本文中,我们将带你了解如何决定Web应用线程池大小在这篇文章中,我们将为您详细介绍如何决定Web应用线程池大小的方方面面,并解答如何决定web应用线程池大小常见的疑惑,同时我们还将给您一些技巧,以帮

在本文中,我们将带你了解如何决定Web应用线程池大小在这篇文章中,我们将为您详细介绍如何决定Web应用线程池大小的方方面面,并解答如何决定web应用线程池大小常见的疑惑,同时我们还将给您一些技巧,以帮助您实现更有效的forkjoinpool 如何设置线程池大小?、IO 密集型的线程池大小设置、IO密集型的线程池大小设置、Java - 如何合理的设置线程池大小

本文目录一览:

如何决定Web应用线程池大小(如何决定web应用线程池大小)

如何决定Web应用线程池大小(如何决定web应用线程池大小)

这篇文章将涉及一个在部署Web应用产品和Web系统性能测试中都会出现的问题:如何决定Web应用的线程池大小?
线程池(Thread Pool)
在Web应用中线程池的大小决定了在任何一个时间点应用可以处理请求的并发数。如果一个系统收到的请求数超过了线程池的大小,那么超出的请求要么进入等待队列要么被拒绝。
请注意,并发和并行是不同的。并发请求是指在任何一个时间点,所有被处理的请求中只有只有很少一部分占用CPU(译者注:轮流使用CPU)。并行是指在任何一个时间点,所有被处理的请求同时在CPU上运行。
在非阻塞式(NO-Blocking)应用中(如NodeJs),一个单独的线程或进程可以并发处理多个请求。而在多核CPU中则可以通过增加线程或进程数来实现并行处理。
在阻塞式IO应用中(如java的SringMVC,一个线程只能同时处理一个并发请求。如果想要并发处理多个请求只能通过增加线程数来实现。
CPU消耗型应用
对于CPU消耗型应用来说,线程池的大小应该和单台服务器的CPU个数相同。对于这类应用由于线程上下文切换增加线程数反而会妨碍对请求的处理,同时还会增加响应时间。
非阻塞式IO应用由于在请求被处理时并不需要等待请求处理完成,因此属于CPU消耗型的应用。
IO消耗型应用
由于IO消耗型应用依赖于下行流量所在系统的响应时间,而且一个线程在其他系统响应完成之前将一直阻塞,所以决定IO消耗型应用的线程池大小变得更加困难。对于这类型应用,我们就像在阻塞式IO应用文章中讲的,通过增加线程数来提高CPU利用率。
科特尔法则(Little’s Law)
科特尔法则通常被用在非技术领域,例如告诉银行柜台出纳员还有多少客户在等待请求处理。
下面是维基百科对科特尔法的说明,英文原文如下:
The average number of threads in a system (Threads) is equal average web request arrival rate (WebRequests per sec), multiplied by the average response time (ResponseTime)
译文:一个系统的平均线程数(线程数)等于平均请求的到达率(每秒请求数)乘以平均响应时间(响应时间)。
公式:线程数=每秒请求数 X 响应时间
公式说明:
线程数   系统所能处理的线程数量
每秒请求数    每秒钟所能处理的请求数
响应时间    处理一个请求所花费的时间
当然,上边的公式给出了处理多少请求需要多少线程,但是并没有考虑线程对CPU的占用率等情况,也没有说明对于多核的单台机器应该分配多少线程。
通过测试决定线程池大小
要分配合适大小的线程池就需要在吞吐量和响应时间这两个要素之间寻求平衡点。从每个CPU最少线程数开始(即线程数=cpu数),系统线程数和平均响应时间成正比直到CPU使用率达到最大或者响应时间不再减少为止。
下图说明了请求数、CPU和响应时间之间的关系。
CPU和请求数的图中展示了随着Web系统负载量不断增加时CPU的使用情况。
响应时间和请求数的图中展示了Web系统负载量的增加对响应时间的影响。
绿色的点表示吞吐量和响应时间的最优点。
线程池大小=CPU核心数



上图展示的是阻塞式IO消耗型应用在线程池大小等于CPU核心数量时的情况。线程由于要等待下行流量的IO处理所以会阻塞,而由于线程的阻塞使响应时间进一步增加,而且即使CPU的占用率非常低,但是线程池中所有线程都处于阻塞状态,那么应用还是会拒绝请求。
大的线程池



上图展示的是阻塞式IO消耗型应用在大的线程池下的使用情况。由于线城池数量大,线程上下文切换也变得非常频繁,而正是这些没必要的上下文切换使得应用还没有达到最大吞吐量时CPU就已经达到最大占用率了。请求响应时间也由于频繁的上下文切换而快速增长。
最优线程池大小



上图展示的是阻塞式IO消耗型应用在最优线程池下的情况。在高吞吐量和更少线程上文切换的情况下CPU得到了高效的利用。同时我们注意到,好的响应时间取决于在线程更少被阻断(上下文切换)的情况下对请求的高效处理。
线程池隔离
在大多数应用中,只有少数类型的请求会比其他请求更耗时,但这少数的耗时请求会影响整个系统的性能。有两个办法可以解决这个问题:
1)  将比较耗时的请求隔离开来专门处理
2)  在同一个应用中为耗时的web请求单独分配一个线程池
决定一个阻塞式IO消耗型应用的最优线程池大小是一件困难的事情,这通常需要通过多个性能测试来决定。如果在一个应用中使用多个线程池,会使对线程池的优化进一步复杂化。

forkjoinpool 如何设置线程池大小?

forkjoinpool 如何设置线程池大小?

public class Demo {
    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = new ForkJoinPool(17);
        for (int i = 0; i < 100; i++) {
            forkJoinPool.execute(() -> {
                System.out.println(Thread.currentThread().getName());
            });
        }
        try {
            TimeUnit.DAYS.sleep(1L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

控制台输出:(只截取了部分)

```text

ForkJoinPool-1-worker-4
ForkJoinPool-1-worker-19
ForkJoinPool-1-worker-18
ForkJoinPool-1-worker-30
ForkJoinPool-1-worker-1
ForkJoinPool-1-worker-23
ForkJoinPool-1-worker-8
ForkJoinPool-1-worker-16
ForkJoinPool-1-worker-25
ForkJoinPool-1-worker-11
ForkJoinPool-1-worker-29
ForkJoinPool-1-worker-9
ForkJoinPool-1-worker-5
ForkJoinPool-1-worker-15
ForkJoinPool-1-worker-22
ForkJoinPool-1-worker-26
ForkJoinPool-1-worker-12

```

IO 密集型的线程池大小设置

IO 密集型的线程池大小设置

类型判断 (CPU密集orIO密集or混合型)

看应用是 CPU 密集型的还是 IO 密集型的,还是混合型的。

  • CPU 密集 CPU 密集型的话,一般配置 CPU 处理器个数 +/-1 个线程,所谓 CPU 密集型就是指系统大部分时间是在做程序正常的计算任务,例如数字运算、赋值、分配内存、内存拷贝、循环、查找、排序等,这些处理都需要 CPU 来完成。

  • IO 密集 IO 密集型的话,是指系统大部分时间在跟 I/O 交互,而这个时间线程不会占用 CPU 来处理,即在这个时间范围内,可以由其他线程来使用 CPU,因而可以多配置一些线程。

  • 混合型 混合型的话,是指两者都占有一定的时间。

IO 密集型线程大小

/**
 * Support class for thread pool size
 * 
 * @author Nadeem Mohammad
 *
 */
public final class ThreadPoolUtil {
	
	private ThreadPoolUtil() {
		
	}
	/**
	 * Each tasks blocks 90% of the time, and works only 10% of its
	 *	lifetime. That is, I/O intensive pool
	 * @return io intesive Thread pool size
	 */
	public static int ioIntesivePoolSize() {
		
		double blockingCoefficient = 0.9;
		return poolSize(blockingCoefficient);
	}

	/**
	 * 
	 * Number of threads = Number of Available Cores / (1 - Blocking
	 * Coefficient) where the blocking coefficient is between 0 and 1.
	 * 
	 * A computation-intensive task has a blocking coefficient of 0, whereas an
	 * IO-intensive task has a value close to 1,
	 * so we don''t have to worry about the value reaching 1.
	 *  @param blockingCoefficient the coefficient
	 *  @return Thread pool size
	 */
	public static int poolSize(double blockingCoefficient) {
		int numberOfCores = Runtime.getRuntime().availableProcessors();
		int poolSize = (int) (numberOfCores / (1 - blockingCoefficient));
		return poolSize;
	}
}

使用

ExecutorService executorService = Executors.newFixedThreadPool(ThreadPoolUtil.ioIntesivePoolSize());

这样语义化设置,表达能力强一些。

doc

  • Java 线程池配置原则

IO密集型的线程池大小设置

IO密集型的线程池大小设置

类型判断(CPU密集orIO密集or混合型)

看应用是CPU密集型的还是IO密集型的,还是混合型的。

  • CPU密集
    CPU密集型的话,一般配置CPU处理器个数+/-1个线程,所谓CPU密集型就是指系统大部分时间是在做程序正常的计算任务,例如数字运算、赋值、分配内存、内存拷贝、循环、查找、排序等,这些处理都需要CPU来完成。

  • IO密集
    IO密集型的话,是指系统大部分时间在跟I/O交互,而这个时间线程不会占用CPU来处理,即在这个时间范围内,可以由其他线程来使用CPU,因而可以多配置一些线程。

  • 混合型
    混合型的话,是指两者都占有一定的时间。

IO密集型线程大小

/**
 * Support class for thread pool size
 * 
 * @author Nadeem Mohammad
 *
 */
public final class ThreadPoolUtil {
    
    private ThreadPoolUtil() {
        
    }
    /**
     * Each tasks blocks 90% of the time, and works only 10% of its
     *    lifetime. That is, I/O intensive pool
     * @return io intesive Thread pool size
     */
    public static int ioIntesivePoolSize() {
        
        double blockingCoefficient = 0.9;
        return poolSize(blockingCoefficient);
    }

    /**
     * 
     * Number of threads = Number of Available Cores / (1 - Blocking
     * Coefficient) where the blocking coefficient is between 0 and 1.
     * 
     * A computation-intensive task has a blocking coefficient of 0, whereas an
     * IO-intensive task has a value close to 1,
     * so we don''t have to worry about the value reaching 1.
     *  @param blockingCoefficient the coefficient
     *  @return Thread pool size
     */
    public static int poolSize(double blockingCoefficient) {
        int numberOfCores = Runtime.getRuntime().availableProcessors();
        int poolSize = (int) (numberOfCores / (1 - blockingCoefficient));
        return poolSize;
    }
}

使用

ExecutorService executorService = Executors.newFixedThreadPool(ThreadPoolUtil.ioIntesivePoolSize());

这样语义化设置,表达能力强一些。

doc

  • Java线程池配置原则

Java - 如何合理的设置线程池大小

Java - 如何合理的设置线程池大小

想要合理配置线程池线程数的大小,需要分析任务的类型,任务类型不同,线程池大小配置也不同。

配置线程池的大小可根据以下几个维度进行分析来配置合理的线程数:

  1. 任务性质可分为:CPU 密集型任务,IO 密集型任务,混合型任务。
  2. 任务的执行时长。
  3. 任务是否有依赖 —— 依赖其他系统资源,如数据库连接等。
  • CPU 密集型任务

尽量使用较小的线程池,一般为 CPU 核心数 + 1。 
因为 CPU 密集型任务使得 CPU 使用率很高,若开过多的线程数,只能增加上下文切换的次数,因此会带来额外的开销。

  • IO 密集型任务

可以使用稍大的线程池,一般为 2*CPU 核心数 + 1。 
因为 IO 操作不占用 CPU,不要让 CPU 闲下来,应加大线程数量,因此可以让 CPU 在等待 IO 的时候去处理别的任务,充分利用 CPU 时间。

  • 混合型任务

可以将任务分成 IO 密集型和 CPU 密集型任务,然后分别用不同的线程池去处理。 
只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。 
因为如果划分之后两个任务执行时间相差甚远,那么先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失

  • 依赖其他资源

如某个任务依赖数据库的连接返回的结果,这时候等待的时间越长,则 CPU 空闲的时间越长,那么线程数量应设置得越大,才能更好的利用 CPU。 

借鉴别人的文章 对线程池大小的估算公式:

       最佳线程数目 = ((线程等待时间 + 线程 CPU 时间)/ 线程 CPU 时间 )* CPU 数目

比如平均每个线程 CPU 运行时间为 0.5s,而线程等待时间(非 CPU 运行时间,比如 IO)为 1.5s,CPU 核心数为 8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。

可以得出一个结论:

线程等待时间所占比例越高,需要越多线程。线程 CPU 时间所占比例越高,需要越少线程。

今天关于如何决定Web应用线程池大小如何决定web应用线程池大小的讲解已经结束,谢谢您的阅读,如果想了解更多关于forkjoinpool 如何设置线程池大小?、IO 密集型的线程池大小设置、IO密集型的线程池大小设置、Java - 如何合理的设置线程池大小的相关知识,请在本站搜索。

本文标签: