名词解释
RT(Response Time): 1个请求所完成的时间
QPS(Query Per Second): 1秒钟内所完成的请求数量
QPS与线程数的关系
对于单线程而言,QPS = 1000ms/RT
比如一个系统只有一个线程,响应时间为50ms,那么它的qps就是1000/50=20
如果它有两个线程,那么它的qps为:20*2=40
这里假设不受cpu、io、内存等其他影响
理论上服务器能够支持的线程数越多,那么qps就会越高,qps与线程数成正比例关系。
当然,服务器的资源是有限的,在实际压测过程中,开始时QPS将随着线程数的增加而增加,当线程数达到一定数量,达到cpu瓶颈时,qps保持不变,随着继续压测,qps还会略微下降,并且响应时间变长。
原因其实很简单,在cpu资源充足时,线程有足够的cpu执行时间用于运行程序,当线程数达到一定数量,同时cpu资源耗尽时,线程开始争抢cpu,频繁发生线程上下文切换(该过程十分耗时),线程之间互相等待,响应时间自然增加。
最佳线程数
通过QPS与线程数的关系,可以很容易的就能得出一个概念。
最佳线程数:刚好消耗完服务器资源的临界线程数。
公式:最佳线程数 = ((线程等待时间 + 线程cpu时间)/ 线程cpu时间) * cpu数量
等价于: 最佳线程数 = (线程等待时间/ 线程cpu时间 + 1) * cpu数量
网上是第一种,等价的公式是我换算的,因为能解释出具体的意义
当然,如果你知道第一种公式的具体意义,还请告诉我
公式说明
假设在单线程情况下,线程等待时间为100ms,线程cpu时间为20ms,在线程等待的100ms,cpu是处于空闲状态,那么我们便可以把这100ms交给其他的线程使用,一共可以给多少个线程使用呢,每个线程需要cpu20ms,那么就是:100/20 = 5, 再加上自己这个线程就是6,所以在这一个时间段内cpu最大可以支配的线程数为6,如果服务器有2个cpu,那么就是6*2 = 12。
套用公式:(10/2 + 1)*2 = 12
当然,在实际执行中,肯定不是他20ms我20ms这样的,而是cpu为每个线程分配时间片交替执行
特性
在达到最佳线程数时,线程数量继续增加,但qps不变,而响应时间变长,继续增加线程数,qps开始下降。
如何得到最佳线程数
1、通过压测的方式,缓慢递增线程数,观察压测情况,根据特性会很容易获得最佳线程数
2、通过公式直接进行计算,这个方式有点难,因为我们难以知道系统的线程cpu时间与线程等待时间
3、根据第一种方式的改进,进行一次压测,观察cpu情况,然后将线程数*(cpu期望值/当前cpu值),就会得到一个大概值,然后略作调整即可得到最佳线程数。
案例
为了更好的认识以上理论,并探讨如何提升QPS,我们通过springboot构建一个测试案例
定义一个用于模拟cpu执行的方法
public long runCpu(int count){ long start = System.currentTimeMillis(); // 用几个参数让cpu运行 int a = 0; double b = 0; long c = 0; for (int i = 0; i < count; i++) { for (int j = 0; j < 100; j++){ a++;b++;c++; a=a*2;b=b/2; a=a/2;b=b*2; c=c*2;c=c/2; a--;b--;c--; } a++;b++;c++; } System.out.println(a); // 返回运行时间 return System.currentTimeMillis() - start; }
count参数使得该方法的运行时间存在可变性
定义压测接口
/** * @param count 循环次数,用于模拟cpu运行时间 * @param sleep io时间 毫秒 */ @GetMapping("/benchmark") public String qps(int count, long sleep) throws InterruptedException { long start = System.currentTimeMillis(); // cpu运行时间 long cpuTime = runCpu(count); long ioStart = System.currentTimeMillis(); // 模拟io阻塞 Thread.sleep(sleep); long ioTime = System.currentTimeMillis() - ioStart; long total = System.currentTimeMillis() - start; return "total: "+ total + " cpu-time:" + cpuTime + " io-time:" + ioTime; }
为了方便测试,我将它做成了镜像,使用docker运行
这是我的docker-compose文件,给了2个cpu
version: '3.5' services: qps-test: image: qps-test:1.0.0 container_name: qps-test ports: - 8080:8080 resources: limits: cpus: '2.00'
第一次测试,将count调为100000(这里相当于我机器的cpu-time为10~20ms),io time为80ms
http://192.168.65.206:8080/qps/benchmark?count=100000&sleep=80
得出结果如下
RT | qps | cpu | 最佳线程数 |
103 | 125 | 190% | 13 |
单线程的QPS: 1000/103 = 9.7
可能会有小伙伴不晓得怎么调出这个结果的,这里我简单说明下
首先我们需要知道,服务器的瓶颈在cpu上,因为我这个案例不可能存在内存瓶颈,所以我们需要将cpu压测到190%左后(临界cpu的瓶颈),如果压到了200%,说明此时线程数很可能已经超了,cpu资源已耗尽,就需要降低线程数,如果没到190%,就继续增加压测线程,直到恒定在190%左右。
压测工具我用的是jmeter
有了这个基准数据,现在就要尝试进行提升qps
优化方向
根据公式:QPS = (1000/RT) * 线程数
由于cpu资源已经将要耗尽,那么我们就只能尝试降低响应时间
而响应时间分为两个部分:cpu时间和线程等待时间,所以我们从这两个方面入手。
降低IO等待时间
我们尝试将io实际从80ms降为40ms
http://192.168.65.206:8080/qps/benchmark?count=100000&sleep=40
进行压测结果如下:
RT | qps | cpu | 最佳线程数 |
65 | 123 | 190% | 8 |
单线程QPS: 1000/65 = 15.4
我们发现响应时间虽然从原来的103变为了65,但qps却几乎未变,而最佳线程数从13变为了8
得出结论:降低IO时间并不能提升QPS,为什么?
我们根据CPU资源恒定原则:CPU资源 = 线程的cpu时间 * 线程总数 * 单线程的qps
所以得出式子:基准数据的cpu每秒的处理时间 = 降低IO等待时间的cpu每秒的处理时间
23ms * 13 * 9.7 = 25ms * x * 15.4 解出 x = 7.53
其中25ms为RT(65) - IO(40) 15.4为1000/65
线程数 | 单线程QPS | RT | CPU处理时间 | QPS |
13 | 9.7 | 103 | 23ms * 13 * 9.7 | 125 |
x ≈ 8 | 15.4 | 65 | 25ms * x * 15.4 | 123 |
降低CPU执行时间
我们将cpu运行时间削减一般,count值100000 -> 50000
http://192.168.65.206:8080/qps/benchmark?count=50000&sleep=80
进行压测结果如下:
RT | qps | cpu | 最佳线程数 |
101 | 244 | 190% | 25 |
单线程qps: 1000/101 = 9.9
响应时间几乎未发生改变,但QPS翻了一倍,最佳线程数也翻了一倍
得出结论:降低cpu时间能显著提升QPS
同样根据CPU资源恒定原则得到:
23ms * 13 * 9.7 = 21ms * x * 9.9
x ≈ 14
由于未知原因,这里翻车了,按理说cpu时间应当为10ms左右,因为count值减半了。
如果cpu时间为10ms~15ms,那么x就接近25,符合压测情况了。这里猜测是因为io时间有误,导致RT变长。
小结
count | sleep | RT | qps | cpu | 最佳线程数 |
100000 | 80ms | 103 | 125 | 190% | 13 |
100000 | 40ms | 65 | 123 | 190% | 8 |
50000 | 80ms | 101 | 244 | 190% | 25 |
QPS与RT的关系
如果说单纯的根据公式:QPS = 1000/RT,QPS与RT的关系如下
但通过案例我们的得知,在实际情况下,QPS与RT的关系并非如此,RT中的存在两种时间对QPS有所影响。
CPU执行时间减少,QPS显著提升。
IO等待时间减少,QPS提升不明显或者无提升。
总结
通过以上内容分析,如果想要提升RT
1、减少IO的响应时间
2、减少CPU的执行时间
如果想要提升QPS
1、减少CPU的执行时间
2、增加CPU数量
提示:如果在压测过程中,cpu还未达到瓶颈,QPS就已经达到了峰值,那么则说明存在其他的瓶颈,如内存
参考资料
https://www.docin.com/p-73662763.html?docfrom=rrela