对于QPS,RT这些名词想必大家都不陌生,但是说到如何提升他们却一筹莫展。今天我们就来研究一下吧
目录
名称解释
QPS与线程数的关系
最佳线程数
案例
优化方向
QPS与RT的关系
总结
名词解释
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/qp...
得出结果如下
RT | qps | cpu | 最佳线程数 |
---|---|---|---|
103 | 125 | 190% | 13 |
可能会有小伙伴不晓得怎么调出这个结果的,这里我简单说明下
首先我们需要知道,服务器的瓶颈在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/qp...
进行压测结果如下:
RT | qps | cpu | 最佳线程数 |
---|---|---|---|
65 | 123 | 190% | 8 |
我们发现响应时间虽然从原来的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运行时间削减一般,count值100000 -> 50000
http://192.168.65.206:8080/qp...
进行压测结果如下:
RT | qps | cpu | 最佳线程数 |
---|---|---|---|
101 | 244 | 190% | 25 |
响应时间几乎未发生改变,但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的关系并非如此,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
追更,想要了解更多精彩内容,欢迎关注公众号:程序员阿紫
个人博客空间:https://zijiancode.cn
【如何提升QPS、RT】如果我的文章对你有所帮助,还请帮忙点赞、转发一下,你的支持就是我更新的动力,非常感谢!
推荐阅读
- 程序员|【高级Java架构师系统学习】毕业一年萌新的Java大厂面经,最新整理
- jvm|【JVM】JVM08(java内存模型解析[JMM])
- c语言|C语言初期学习遇到的特殊点 【三子棋详解】【初学者福音,详细总结,复习能手】
- Python|Python实战(使用线性回归预测房价)
- IC|数字IC后端真的不如前端设计和验证吗()
- Python|教你写个简单好用的Python脚本一键自动整理文件非常适合办公用~
- python|oeasy教您玩转python - 007 - # 字符本质
- vue.js|后端开发学习Vue(一)
- Go|Docker后端部署详解(Go+Nginx)
- 后台|NATAPP内网穿透通过nginx实现一个端口访问多个不同端口服务