首页 » 未分类 » 正文

压测 – 并发数与QPS

今天,一个同事与我探讨一个关于压测的问题,他在一个业务项目压测的过程中,发现并发数最多只能达到30,这个并发数无论如何都没法再增加,他很诧异,凭经验,一个业务项目QPS能达到几百到几千是非常常见的。

首先,我们探讨了一下什么是并发数,什么是QPS?

并发数:并发数指在压测过程中,同时可以向待测试服务发送请求的线程数,通常情况下,测试前由测试工具创建好每个线程,并且在内存中创建好要发送请求的对象,测试开始后,每个线程同时向待测试服务发送请求。

QPS:指服务器在一秒的时间里能够处理的请求数。

由定义可见,并发数和QPS是不同的概念,代表着不同的意义。 并发数高并不能代表QPS就一定高,反之亦然。合理的并发数可以达到最理想的QPS。

同事提供了一份压测时候jstack的输出,来证明并发数到30确实达到了瓶颈:

Java Stack Trace文件

简单的浏览了线程堆栈文件,可以看到使用的是tomcat bio,并且线程号已经滚动到3122。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"http-bio-8058-exec-3122" daemon prio=10 tid=0x00007fd51c32d000 nid=0x5f20 waiting on condition [0x00007fd525806000]
   java.lang.Thread.State: TIMED_WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x0000000780cf6820> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:226)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2082)
        at java.util.concurrent.LinkedBlockingQueue.poll(LinkedBlockingQueue.java:467)
        at org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:86)
        at org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:32)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1068)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

然后,统计了总的线程数量:

1
2
3
robert at localhost in ~/Desktop
$ grep "java.lang.Thread.State" stack.txt | wc -l
     533

发现,当前有533个线程在运行。

先排除是否线程有阻塞情况,统计了总的可执行线程的数量:

1
2
3
robert at localhost in ~/Desktop
$ grep "RUNNABLE" stack.txt | wc -l
     426

由此可见,当前有426个线程在执行,没有等待,没有锁。那么,当前的任务,我们要看一下当前可执行线程都在做什么?

这时观察可执行线程的线程堆栈,获知这些线程都在读取socket:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
"http-bio-8058-exec-3108" daemon prio=10 tid=0x0000000001249800 nid=0x5f10 runnable [0x00007fd525d9c000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.read(SocketInputStream.java:152)
        at java.net.SocketInputStream.read(SocketInputStream.java:122)
        at org.apache.coyote.http11.InternalInputBuffer.fill(InternalInputBuffer.java:516)
        at org.apache.coyote.http11.InternalInputBuffer.fill(InternalInputBuffer.java:501)
        at org.apache.coyote.http11.Http11Processor.setRequestLineReadTimeout(Http11Processor.java:167)
        at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:946)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
        at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316)
        - locked <0x00000007eb012fc8> (a org.apache.tomcat.util.net.SocketWrapper)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
        - <0x00000007be3bf730> (a java.util.concurrent.ThreadPoolExecutor$Worker)

然后统计了一下,读取socket的线程数量:

1
2
3
robert at localhost in ~/Desktop
$ grep "java.net.SocketInputStream.socketRead0" stack.txt | wc -l
     415

可见,当前415个可执行线程在读网络请求,也就是请求量足够大让tomcat线程池创建几百个线程来处理请求,这也证明客户端的压测虽然并发数只有30,但是,tomcat服务器接受的请求量并不只30,也就是当前并发处理的请求就有415个,那是不是说QPS就是415了呢?

这也不一定,这取决于处理一个任务的响应时间,假设每个任务需要1s处理完成,那么服务器端的QPS就是415,假如每个任务需要500ms处理完成,那么QPS就应该翻倍。

因此,并发数和QPS是两个既相关又有区别的两个参数,评价一个服务的质量,主要由QPS和响应时间来衡量,并发数只是为了达到最好的QPS和响应时间而设置的参数,是做系统性能评估应该考虑的一个重要前提条件。

我们做压测的目的并不是要获得最高的并发数,而是找到那个最合适的并发数,让QPS和响应时间达到最佳的状态。