对于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/qps/benchmark?count=100000&sleep=80

得出结果如下

RTqpscpu最佳线程数
103125190%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

进行压测结果如下:

RTqpscpu最佳线程数
65123190%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

线程数单线程QPSRTCPU处理时间QPS
139.710323ms * 13 * 9.7125
x ≈ 815.46525ms * x * 15.4123

降低CPU执行时间

我们将cpu运行时间削减一般,count值100000 -> 50000

http://192.168.65.206:8080/qps/benchmark?count=50000&sleep=80

进行压测结果如下:

RTqpscpu最佳线程数
101244190%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变长。

小结

countsleepRTqpscpu最佳线程数
10000080ms103125190%13
10000040ms65123190%8
5000080ms101244190%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