對(duì)于QPS,RT這些名詞想必大家都不陌生,但是說(shuō)到如何提升他們卻一籌莫展。今天我們就來(lái)研究一下吧
目錄
名詞解釋
RT(Response Time): 1個(gè)請(qǐng)求所完成的時(shí)間
QPS(Query Per Second): 1秒鐘內(nèi)所完成的請(qǐng)求數(shù)量
QPS與線程數(shù)的關(guān)系
對(duì)于單線程而言,QPS = 1000ms/RT
比如一個(gè)系統(tǒng)只有一個(gè)線程,響應(yīng)時(shí)間為50ms,那么它的qps就是1000/50=20
如果它有兩個(gè)線程,那么它的qps為:20*2=40
這里假設(shè)不受cpu、io、內(nèi)存等其他影響
理論上服務(wù)器能夠支持的線程數(shù)越多,那么qps就會(huì)越高,qps與線程數(shù)成正比例關(guān)系。

當(dāng)然,服務(wù)器的資源是有限的,在實(shí)際壓測(cè)過(guò)程中,開(kāi)始時(shí)QPS將隨著線程數(shù)的增加而增加,當(dāng)線程數(shù)達(dá)到一定數(shù)量,達(dá)到cpu瓶頸時(shí),qps保持不變,隨著繼續(xù)壓測(cè),qps還會(huì)略微下降,并且響應(yīng)時(shí)間變長(zhǎng)。

原因其實(shí)很簡(jiǎn)單,在cpu資源充足時(shí),線程有足夠的cpu執(zhí)行時(shí)間用于運(yùn)行程序,當(dāng)線程數(shù)達(dá)到一定數(shù)量,同時(shí)cpu資源耗盡時(shí),線程開(kāi)始爭(zhēng)搶cpu,頻繁發(fā)生線程上下文切換(該過(guò)程十分耗時(shí)),線程之間互相等待,響應(yīng)時(shí)間自然增加。
最佳線程數(shù)
通過(guò)QPS與線程數(shù)的關(guān)系,可以很容易的就能得出一個(gè)概念。
最佳線程數(shù):剛好消耗完服務(wù)器資源的臨界線程數(shù)。
公式:最佳線程數(shù) = ((線程等待時(shí)間 + 線程cpu時(shí)間)/ 線程cpu時(shí)間) * cpu數(shù)量
等價(jià)于: 最佳線程數(shù) = (線程等待時(shí)間/ 線程cpu時(shí)間 + 1) * cpu數(shù)量
網(wǎng)上是第一種,等價(jià)的公式是我換算的,因?yàn)槟芙忉尦鼍唧w的意義
當(dāng)然,如果你知道第一種公式的具體意義,還請(qǐng)告訴我
公式說(shuō)明
假設(shè)在單線程情況下,線程等待時(shí)間為100ms,線程cpu時(shí)間為20ms,在線程等待的100ms,cpu是處于空閑狀態(tài),那么我們便可以把這100ms交給其他的線程使用,一共可以給多少個(gè)線程使用呢,每個(gè)線程需要cpu20ms,那么就是:100/20 = 5, 再加上自己這個(gè)線程就是6,所以在這一個(gè)時(shí)間段內(nèi)cpu最大可以支配的線程數(shù)為6,如果服務(wù)器有2個(gè)cpu,那么就是6*2 = 12。
套用公式:(10/2 + 1)*2 = 12
當(dāng)然,在實(shí)際執(zhí)行中,肯定不是他20ms我20ms這樣的,而是cpu為每個(gè)線程分配時(shí)間片交替執(zhí)行
特性
在達(dá)到最佳線程數(shù)時(shí),線程數(shù)量繼續(xù)增加,但qps不變,而響應(yīng)時(shí)間變長(zhǎng),繼續(xù)增加線程數(shù),qps開(kāi)始下降。
如何得到最佳線程數(shù)
1、通過(guò)壓測(cè)的方式,緩慢遞增線程數(shù),觀察壓測(cè)情況,根據(jù)特性會(huì)很容易獲得最佳線程數(shù)
2、通過(guò)公式直接進(jìn)行計(jì)算,這個(gè)方式有點(diǎn)難,因?yàn)槲覀冸y以知道系統(tǒng)的線程cpu時(shí)間與線程等待時(shí)間
3、根據(jù)第一種方式的改進(jìn),進(jìn)行一次壓測(cè),觀察cpu情況,然后將線程數(shù)*(cpu期望值/當(dāng)前cpu值),就會(huì)得到一個(gè)大概值,然后略作調(diào)整即可得到最佳線程數(shù)。
案例
為了更好的認(rèn)識(shí)以上理論,并探討如何提升QPS,我們通過(guò)springboot構(gòu)建一個(gè)測(cè)試案例
定義一個(gè)用于模擬cpu執(zhí)行的方法
public long runCpu(int count){
long start = System.currentTimeMillis();
// 用幾個(gè)參數(shù)讓cpu運(yùn)行
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);
// 返回運(yùn)行時(shí)間
return System.currentTimeMillis() - start;
}
count參數(shù)使得該方法的運(yùn)行時(shí)間存在可變性
定義壓測(cè)接口
/**
* @param count 循環(huán)次數(shù),用于模擬cpu運(yùn)行時(shí)間
* @param sleep io時(shí)間 毫秒
*/
@GetMapping("/benchmark")
public String qps(int count, long sleep) throws InterruptedException {
long start = System.currentTimeMillis();
// cpu運(yùn)行時(shí)間
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;
}
為了方便測(cè)試,我將它做成了鏡像,使用docker運(yùn)行
這是我的docker-compose文件,給了2個(gè)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'
第一次測(cè)試,將count調(diào)為100000(這里相當(dāng)于我機(jī)器的cpu-time為10~20ms),io time為80ms
http://192.168.65.206:8080/qps/benchmark?count=100000&sleep=80
得出結(jié)果如下
| RT | qps | cpu | 最佳線程數(shù) |
|---|---|---|---|
| 103 | 125 | 190% | 13 |
單線程的QPS: 1000/103 = 9.7
可能會(huì)有小伙伴不曉得怎么調(diào)出這個(gè)結(jié)果的,這里我簡(jiǎn)單說(shuō)明下
首先我們需要知道,服務(wù)器的瓶頸在cpu上,因?yàn)槲疫@個(gè)案例不可能存在內(nèi)存瓶頸,所以我們需要將cpu壓測(cè)到190%左后(臨界cpu的瓶頸),如果壓到了200%,說(shuō)明此時(shí)線程數(shù)很可能已經(jīng)超了,cpu資源已耗盡,就需要降低線程數(shù),如果沒(méi)到190%,就繼續(xù)增加壓測(cè)線程,直到恒定在190%左右。
壓測(cè)工具我用的是jmeter
有了這個(gè)基準(zhǔn)數(shù)據(jù),現(xiàn)在就要嘗試進(jìn)行提升qps
優(yōu)化方向
根據(jù)公式:QPS = (1000/RT) * 線程數(shù)
由于cpu資源已經(jīng)將要耗盡,那么我們就只能?chē)L試降低響應(yīng)時(shí)間
而響應(yīng)時(shí)間分為兩個(gè)部分:cpu時(shí)間和線程等待時(shí)間,所以我們從這兩個(gè)方面入手。
降低IO等待時(shí)間
我們嘗試將io實(shí)際從80ms降為40ms
http://192.168.65.206:8080/qps/benchmark?count=100000&sleep=40
進(jìn)行壓測(cè)結(jié)果如下:
| RT | qps | cpu | 最佳線程數(shù) |
|---|---|---|---|
| 65 | 123 | 190% | 8 |
單線程QPS: 1000/65 = 15.4
我們發(fā)現(xiàn)響應(yīng)時(shí)間雖然從原來(lái)的103變?yōu)榱?5,但qps卻幾乎未變,而最佳線程數(shù)從13變?yōu)榱?
得出結(jié)論:降低IO時(shí)間并不能提升QPS,為什么?
我們根據(jù)CPU資源恒定原則:CPU資源 = 線程的cpu時(shí)間 * 線程總數(shù) * 單線程的qps
所以得出式子:基準(zhǔn)數(shù)據(jù)的cpu每秒的處理時(shí)間 = 降低IO等待時(shí)間的cpu每秒的處理時(shí)間
23ms * 13 * 9.7 = 25ms * x * 15.4 解出 x = 7.53
其中25ms為RT(65) - IO(40) 15.4為1000/65
| 線程數(shù) | 單線程QPS | RT | CPU處理時(shí)間 | QPS |
|---|---|---|---|---|
| 13 | 9.7 | 103 | 23ms * 13 * 9.7 | 125 |
| x ≈ 8 | 15.4 | 65 | 25ms * x * 15.4 | 123 |
降低CPU執(zhí)行時(shí)間
我們將cpu運(yùn)行時(shí)間削減一般,count值100000 -> 50000
http://192.168.65.206:8080/qps/benchmark?count=50000&sleep=80
進(jìn)行壓測(cè)結(jié)果如下:
| RT | qps | cpu | 最佳線程數(shù) |
|---|---|---|---|
| 101 | 244 | 190% | 25 |
單線程qps: 1000/101 = 9.9
響應(yīng)時(shí)間幾乎未發(fā)生改變,但QPS翻了一倍,最佳線程數(shù)也翻了一倍
得出結(jié)論:降低cpu時(shí)間能顯著提升QPS
同樣根據(jù)CPU資源恒定原則得到:
23ms * 13 * 9.7 = 21ms * x * 9.9
x ≈ 14
由于未知原因,這里翻車(chē)了,按理說(shuō)cpu時(shí)間應(yīng)當(dāng)為10ms左右,因?yàn)閏ount值減半了。
如果cpu時(shí)間為10ms~15ms,那么x就接近25,符合壓測(cè)情況了。這里猜測(cè)是因?yàn)閕o時(shí)間有誤,導(dǎo)致RT變長(zhǎng)。
小結(jié)
| count | sleep | RT | qps | cpu | 最佳線程數(shù) |
|---|---|---|---|---|---|
| 100000 | 80ms | 103 | 125 | 190% | 13 |
| 100000 | 40ms | 65 | 123 | 190% | 8 |
| 50000 | 80ms | 101 | 244 | 190% | 25 |
QPS與RT的關(guān)系
如果說(shuō)單純的根據(jù)公式:QPS = 1000/RT,QPS與RT的關(guān)系如下

但通過(guò)案例我們的得知,在實(shí)際情況下,QPS與RT的關(guān)系并非如此,RT中的存在兩種時(shí)間對(duì)QPS有所影響。
CPU執(zhí)行時(shí)間減少,QPS顯著提升。
IO等待時(shí)間減少,QPS提升不明顯或者無(wú)提升。
總結(jié)
通過(guò)以上內(nèi)容分析,如果想要提升RT
1、減少I(mǎi)O的響應(yīng)時(shí)間
2、減少CPU的執(zhí)行時(shí)間
如果想要提升QPS
1、減少CPU的執(zhí)行時(shí)間
2、增加CPU數(shù)量
提示:如果在壓測(cè)過(guò)程中,cpu還未達(dá)到瓶頸,QPS就已經(jīng)達(dá)到了峰值,那么則說(shuō)明存在其他的瓶頸,如內(nèi)存
參考資料
https://www.docin.com/p-73662763.html?docfrom=rrela
追更,想要了解更多精彩內(nèi)容,歡迎關(guān)注公眾號(hào):程序員阿紫
個(gè)人博客空間:https://zijiancode.cn
如果我的文章對(duì)你有所幫助,還請(qǐng)幫忙點(diǎn)贊、轉(zhuǎn)發(fā)一下,你的支持就是我更新的動(dòng)力,非常感謝!