对于一个网站,已知服务端的服务线程数和处理单个请求所需的时间时,该如何算出高并发时用户从点击链接到收到响应的时间?注意这个时间并不等于服务端处理单个请求的时间,因为高并发时,很多用户请求需要排队等待,你要把这个额外的等待时间算进去。
这个问题很重要,因为它的结果直接影响你的网站的用户体验。这篇文章就是来帮你算这个时间的。你可以使用本文附带的程序来算,也可以通过本文提炼出的公式来算。
另外还有一个问题:所谓RT(响应时间)和QPS,究竟要不要考虑用户请求的排队等待时间? 本文认为,对于RT我们应该区分出“服务器眼中的RT”和“客户端眼中的RT”,但对于QPS却不必区分。
后文会辨析这些概念,同时还会给出不同场景下,RT和QPS随着并发数变化而变化的曲线图。你可以通过比较不同的曲线,获得一些收获。
下面先推演一个最简单的场景,通过这个场景,你可以了解到本文在计算时使用的基本思路。
最简单的场景:服务端线程数=1,客户端并发=2
如果服务端只有1个服务线程,每处理1个请求需要1个ms; 当客户端在同一瞬间提交两个请求时,各项性能指标是怎样的?
为了解这个题,可以想象一下每个请求被处理的过程:
请求1被立即处理;由于服务线程只有一个,请求2需要等待请求1完结后才能被处理。
据此算出各项性能指标:
性能指标 | 指标值 | 说明 |
服务端响应所有请求的时间 | 1ms + 1ms = 2ms | 上图中紫色时间之和 |
服务端响应每个请求的平均时间 | 服务端处理所有请求的时间/2 = 1ms |
|
客户端所有请求的等待、响应时间 | 1ms + (1ms + 1ms) = 3ms | 第1个请求直接处理 = 1ms 第2个请求先等待后处理 = 1ms + 1ms |
客户端每个请求的平均等待、响应时间 | 客户端所有请求的等待、响应时间/2 = 1.5 ms |
|
看上面高亮的这些字,你应该已经了解了时间推算的基本思路了;后文其他场景中会使用相同的模式来推算。
再来观察一下客户端每个请求的平均等待、响应时间。它的值是1.5ms,大于服务端响应每个请求的平均时间1ms.
不同角度有不同的看法,
1. 服务端站在自己的角度来看:并发为2时,处理每个请求消耗了自己1ms, RT = 1ms
2. 客户端站在自己的角度来看:并发为2时,每个请求平均需要花1.5ms才能被完结,服务器的RT应用1.5ms ,比服务端宣称的1ms要高多了。
这两种说法都有道理。就像银行柜员一直在忙,没有偷懒,问心无愧;而排队的客户苦苦等待,牢骚漫天,其实也很无辜。
因此本文认为,RT应分为两种:
性能指标 | 指标值 | 说明 |
服务端眼中的RT | 1ms | 不计入客户端的等待时间 |
客户端眼中的RT | 1.5ms | 计入客户端的等待时间 |
顺便再算一下此时的QPS. 事实上无论在服务端眼里还是客户端眼里,QPS都是:
成功处理的请求数/(最后一个请求结束时间 – 第一个请求开始时间)= 2 / (2 – 0)ms = 1000
接下来再看看其它场景。
场景2:服务端线程数 > 1
上文说了服务端线程数=1的情况,如果服务端线程数超过1会怎么样? 比如,服务端线程数 = 3, 客户端并发 = 10:
指标值 | |
服务端响应所有请求的时间 | 1ms * 3 + 1ms * 3 + 1ms * 3 + 1ms = 10ms |
服务端响应每个请求的平均时间 | 服务端处理所有请求的时间/10 = 1ms |
客户端所有请求的等待、响应时间 | 1ms * 3 + 2ms * 3 + 3ms * 3 + 4ms = 22ms |
客户端所有请求的等待、响应时间/10 = 2.2 ms |
这个推算并不难,但如果总是需要这样手工推算就很烦了。因此,本文提供了一个程序来将推算步骤自动化。不过,在体验这个程序之前,我们先推演一下其他的场景。
场景3:无过载保护,服务器响应能力受并发数影响
以上的场景都假设系统有严格的“过载保护”:只提供有限的服务线程数,由于系统压力不会过载,所以每个线程都总是能以最高的速度来处理请求。
现实中,很多系统并没有这种做,相反:
1. 没有过载保护,来一个请求启一个线程,直到系统崩溃
2. 当客户端并发数超过一定阈值后,服务端处理单个请求的时间开始上升(原因很多,比如CPU、内存资源消耗过大,线程上下文切换过多等)
举个例子,一个应用在并发<=3时,处理单个请求只需1ms;当并发超过3时,处理单个请求的能力开始恶化,
3个并发 => 处理单个请求需1ms
5个并发 => 处理单个请求需要5ms
…
10个并发 => 处理单个请求需要10ms
如果客户端并发=10,性能会是什么状况?
性能指标 | 指标值 |
服务端响应所有请求的时间 | 10 * 10ms = 100ms |
服务端响应每个请求的平均时间 | 服务端处理所有请求的时间/10 = 10ms |
客户端所有请求的等待、响应时间 | 10 * 10ms = 100ms |
客户端每个请求的平均等待、响应时间 | 客户端所有请求的等待、响应时间/10 = 10 ms |
没有过载保护时,所有请求都不用排队;这时推算性能的算法会比较简单。接下来再看最后一种场景。
场景4:弱过载保护
有很多应用,在过载保护方面是上面两种机制的折衷:
1. 在客户端并发数低于某阈值时,单个请求的处理速度可以保持全速
2. 当客户端并发数高于上述阈值但还不太高时,单个请求的处理速度有所下降,但仍可以接受,系统这时愿意容忍这种程度的并发,以换取更高的吞吐量(QPS)
3. 当客户端并发数过大、高于另一个阈值时,单个请求的处理速度会迅速恶化,应该在这个点进行过载保护
本文把这种策略称作“弱过载保护”,场景2称作“强过载保护”,场景3则是“无过载保护”。
弱过载保护系统的性能该如何推算?
1. 并发数低于第一个阈值时,所有请求不必等待。此时的计算方法跟“无过载保护”基本相同,只不过这时单个请求的处理时间是个常量。
2. 并发数高于第一个阈值但低于第二个阈值时,所有请求仍不必等待,此时的计算方法跟“无过载保护”完全一致。
3. 并发数高于第二个阈值时,有些请求开始等待,此时的计算方法跟“强过载保护”场景下的模型基本一致,只不过这时单个请求的处理时间 = 最高并发时的单个请求的处理时间。
另外,“强过载保护”和“无过载保护”可以视为“弱过载保护”的特殊情况。
1. “强过载保护”也是一种“弱过载保护”,它的第二个阈值 = 第一个阈值
2. “无过载保护”也是一种“弱过载保护”,它的二级阈值 = ∞
既然所有的场景都可以统一为同一种机制,我们可以搞出一个“统一的性能 - 并发数计算模型”。
统一的性能 - 并发数计算模型
输入:
| 符号 | 公式中的符号 | |
服务器端处理能力 | 第1个线程数阈值 | serverThreshold1 | |
第1个线程数阈值前的单个请求处理时间 | serverResponse1 | ||
第2个线程数阈值 | serverThreshold2 | ||
两个阈值之间的单个请求处理时间函数 | serverResponse2Func(clientConcurrency) | ||
客户端压力 | 客户端并发数 | clientConcurrency |
输出:
| 符号 | 公式中的符号 | 备注 | |
服务器眼中的性能 | 服务器响应所有请求的时间 | serverResponseSum |
| |
服务器响应每个请求的平均时间 | serverResponseAverage | serverResponseSum/clientConsurrency | ||
客户端眼中的性能 | 客户端所有请求的等待、响应时间 | clientWaitResponseSum |
| |
客户端每个请求的平均等待、响应时间 | clientWaitResponseAverage | clientWaitResponseSum/clientConsurrency |
算法:
参照下图,推算出每个请求的处理时间和响应时间,然后分别求和、求平均.
通过程序自动化计算
按照上图的模式来手工推演性能会很烦的。本文提供了一个开源程序,以将推算过程自动化。
如果急于看效果,可以
2. 执行 java -jar how-long-to-wait-1.0.0-SNAPSHOT-jar-with-dependencies.jar 这个DEMO算出了serverThreshold =3, 并发=10的各项性能指标
想自定义参数,则可以:
1. 下载http://how-long-to-wait.googlecode.com/files/how-long-to-wait-1.0.0-SNAPSHOT-project.zip
2. 解压,作为Maven工程放到你的IDE里
3. 模仿org.googlecode.hltwsample.single.ShowCase写你自己的代码
这个程序还会记录每个请求被执行的起止时间,你可以利用它来验算
…
默认情况下执行时间会被直接打印到控制台,但你也可以扩展实现自己的记录展现工具。具体方法是,
1. 实现SingleAppSimulateEventListener接口
2. 再把它注入到singleAppVisitSimulator.setEventListener()
通过公式计算
你也可以直接通过公式来计算性能。
服务端眼中的RT:
客户端眼中的RT:
得
性能-并发数曲线图
前面一直在做定量分析,现在可以搞一下定性分析了:从数据中找到可以辅助决策的结论。
我们可以让并发数持续升高,同时观察服务器/客户端眼中性能的差异,以及同一性能指标在不同过载保护机制下的差异。
服务端处理能力:
第1个线程数阈值 | 10 |
第1个线程数阈值前的单个请求处理时间 | 10ms |
第2个线程数阈值 | 20 |
单个请求处理时间函数 |
无过载保护时的性能趋势图
1. 并发数超过一个极限之前,RT基本不变,但QPS会持续上升,表明系统的并发服务能力被利用得越来越充分
2. 无过载保护时,当并发数超过一个极限后,系统性能会急剧下降
3. 无过载保护时,所有请求会被服务端立即处理,没有等待时间,因此客户端和服务器的RT曲线完全吻合。
强过载保护时的性能趋势图
1. 强过载保护时,服务器跟中的永远是条水平线,因为服务器一直处于最佳状态
2. 强过载保护时,客户端眼中的系统性能在并发数经过一个阈值后逐渐恶化,实际上这只是排队时间在增多而已。
3. 强过载保护时,QPS的变化比较平稳,因为服务器一直处于健康状态
弱过载保护时的性能趋势图
1. 弱过载保护时,RT会先后经历两次较为平缓的性能下降。第一次是由于服务器本身性能下降,第二次则是因为客户端的排队时间增多。
2. 弱过载保护时,QPS的变化仍算比较平稳
不同过载保护机制下的性能比较
放大下面两条曲线:
在服务器眼里,保护越强,则性能越好
放大下面两条曲线:
1. 在客户端眼里,有过载保护比没有好
2. 在上图中,弱过载保护时的性能比强过载保护时的RT要平一些,但这是因为本图里的函数本身比较平坦,使得系统能够在较高并发时仍以较好的速度处理请求,而且总体性能更好。 如果
函数很陡的话,情况就会是另外一种样子了。所以,强过载保护好还是弱过载保护好,要具体案例具体分析。
结语
本文给出的程序和公式可以帮助你在已知服务端性能的条件下,计算出客户端的平均等待、相应时间。
本文区别了服务器眼中的RT和客户端眼中的RT,并且通过曲线图介绍了不同过载保护机制下性能如何随并发数变化而变化。
不过,本文的推算剧本中,客户端只会发一次请求:若并发为10,则一次性提交10个请求。而现实中的客户端应该会持续不断的提交请求,这时候它们的平均等待、响应时间又是多少呢? 这个问题要结合客户端本身发送请求的速率来计算,我们以后再深入地看看。