压力测试与服务容量规划

压力测试的目标主要有两个,一是验证服务技术指标是否符合要求,二是为服务的实际部署方案的规划提供基础数据,以便回答这个看上去很简单的问题:要什么配置的服务器?要几台才够用?然而这个问题其实并不那么简单,因为服务规划并不是简单的四则运算。因为其相关指标之间是有相互影响的,会需要做些权衡取舍。
为了达到以上两个目标,压力测试的一般步骤可以这样做:

  1. 确定服务能达到的最小响应时间。这个一般是在低压力情况下得出。
    • 同时,根据最低响应时间,我们可以大体计算出单个用户,单服务节点的最大QPS。
  2. 在高并发的情况下,测算出单个服务节点所能承载的最大QPS。
  3. 通过模拟真实请求的使用模式,测算出目标响应时间下的最大并发用户数。
  4. 验证上述最大并发用户数下,长时间运行的服务稳定性。同时系统负载小于60%(后面解释)

真实请求模式的模拟

这一点非常重要。因为测试时请求模式的不同,会导致压力测试的结果的差异非常大,尤其是系统响应时间。请求模式真实与否的主要考虑点有以下三个:

  • 请求发出的时间点,是否符合平均分布。而不是出现一个个的请求量尖峰。
  • 每个连接发出的请求间,是否有合理的间隔(也称为思考时间)。而不是全火力发请求。
  • 模拟过程中,系统负载也要控制在合理范围内

我们做这样的一个最理想情况下的思想实验。假设一个接口的响应时间是1ms,支持的并发量为1(即一次只能处理一个请求),用1000个请求来做压力测试。我们来讨论一下两种不同的请求发送模式:

  • 模式一:假想这1000个请求是每隔1ms发一个过来。那么每个请求发来的时候,上一下都刚好处理完,新请求都会毫无延迟地得到处理。从客户端看来,这1000个请求的响应时间都是1ms。
  • 模式二:假想这1000个请求是在一开始一起发到服务器端。第一个请求会被及时处理,之后每个请求的等待时间都比前一个多1ms,到第1000个请求,要等999ms才会被开始处理。从客户端看来,这1000个请求,虽然和模式一一样,也是在1秒内全部处理完成,但是平均响应时间是500.5ms。

可以看到,在上面的两种不同请求模式中,最终测试出来的响应时间相差500倍。当然,这个例子是理想情况下的两个极端。现实中的差别不会这么大,但是也非常可观。由于压力测试脚本产出的请求模式的不同,在结果上产生几十倍差距的情况也是屡见不鲜的。

系统负载与响应时间的关系

在上一个思想实验的模式一中,我们发送请求是均匀分布的,但是更真实的分布,是时间齐次的一维泊松分布(一种随机分布)。有什么不同呢?请求平均分布的情况下,我们可以想象,只要负载没达到100%,响应时间就应该一直是1ms。但是在真实的情况下,我们这个假想接口的响应时间会是系统负载的一个函数。关系函数如下(其中,s表示请求处理时间,p表示系统使用率,r表示响应时间。):

r=s(2-p)/2(1-p) 

函数图像如下:

响应时间随负载的变化

我们可以看到,负载在80%的时候,响应时间就会是三倍于处理时间,而负载在90%以上时,延迟就会直接彪高。

注意这个公式并不具有普遍适用性,只能用于等候理论中,符合M/D/1型队列的情况,而且是假设服务器本身的性能不会因为负载的变化而变化。而真实的情况,会比这个更加复杂,服务接口的处理时间,常常本身也是负载的一个函数。通过这个分析及图表想要说明的是,如果想要保证系统的延迟在某个范围内,就要确保服务本身的负载不能过高。

一开始说,压力测试的一个目标是验证服务的技术指标是否达标,但是技术指标有很多,不同指标的压力测试方式也是不同的。不可能用一次压力测试,把所有的指标都能验证完成。如果想测试系统延迟是否达标,就要在测试过程中保重负载不过高。要知道,实际生产环境中,一般负载大于60%就要开始着手扩容了。根本不允许负载过高的情况出现。压力测试,还可以验证,在负载小于某个点时,比如60%,能撑住多大的量。用100%去测试,但是生产又不跑到100%,测试的效果就要打折扣。100%负载的压力测试当然也有用,可以用来验证服务在高强负载的情况下,服务的可用性、稳定性是否依然符合要求,不能宕机,不能无响应,不出错,基本上就可以说稳定性就是好的,但是这个时候的系统延迟是不可能好看的。

对压力测试一个常见的误解就是:压力测试嘛,时时刻刻的负载都得是100%才叫压力测试。

集群测试的目的

到4为止,对单台服务器的测试基本完成。后面就可以开始集群测试了。集群测试的目的是,验证服务的水平可扩展性是否良好。水平可扩展性良好的服务,加一倍的容量,就能多承载一倍的负载。水平可扩展性不好的服务,多加多少节点,都没办法支撑更多的负载。造成水平扩展不良的的原因,一般都是服务内部有核心操作串行化。更常见的情况是:加一倍的容易,可以多承载X倍的负载(其中因子X小于1)。对集群进行压力测试的主要目标,就是测算出这个X因子的值。以用于服务容量规划。在实际的规划过程中,会有两个方向:

  1. 保目标响应时间。一般用于对响应时间要求高的系统。就直接用最佳并发用户数去做规划就好。最最理想的情况下,如果1台能支撑100用户,10台能支持最多1000用户。
  2. 保每台最大并发用户数,以节省服务器资源。如果可接受的响应时间比较高,那么每台服务器就可以承载更多的用户。这时需要再进行一次,以测算单台服务器的最大并发用户数为目标的压力测试。并以这个最大并发用户数去做容量规划。

所有服务容量规划的前提,都是服务本身是可以水平扩展的。

下面以K6压力测试的结果为例,简单演示一下以上过程。

示例测试环境

Mock的被测试服务接口如下所示。没有数据库访问。没有同步。这样做的主要目的是简化模型。现实情况的结果,会比下面的演示复杂得多。

@GetMapping("/say")
fun sayHello(@RequestParam greeting: String): Mono<Any> {
    return Mono.just(greeting)
}

运行环境:Docker 4 Core 4GB on Mac Book Pro with 2.8G i7 CPU 同环境还运行:Spring Cloud Gateway, Prometheus, Grafana, InfluxDb及K6 Test Engine。

最小响应时间的测量

测试方法:使用少量用户(比如一两个),无思考时间(全负荷)发请求。

期望结果:响应时间保持极低水平。QPS也要足够高。

如下图所示:

平均响应1ms,95%分位1.5ms。后文为便于计算,最低响应时间记为1ms。 下图中,QPS最高1500。但是可以想象,这并不是这台服务的最大QPS,毕竟用户太少了。 根据响应时间,我们大体可以知道,单个用户,在单个服务节点上,最大QPS是1000。

最大QPS的测量

测试方法:使用大量用户(使用10,30,100,300每别测算),无思考时间发请求。

期望结果:一开始增加并发用户量,可以显著提高QPS压力,但是用户增加到几十个(甚至几个)之后,QPS基本就上不去了。

如下图所示:

我们用100个高并发用户去压,并没有把QPS压到100*1000这个量级。我们发现最高也就到3200QPS,就上不去了。而且服务的响应延迟显著增加。这是非常正常的。因为无思考时间的用户,等同于在做DoS攻击。正常用户的正常API调用,是不可能出现这种全负载狂发请求的情况的。如果你说你的API特殊,就是每个用户要1秒调用1000次以上,那你其实最好重新思考下这个设计是否合理。

真实负载下的并发用户数的测算

上面我们已经讨论过什么是真实负载。于是在这个压力测试中,我们要引入思考时间这个概念。

测试方法:从0开始,逐渐增加用户数到200。加入50ms的思考时间(即每个用户,每秒最多发20个请求)

期望结果:观察响应时间的增长情况,用户量达到某个点后,响应时间开始快速增长。这个点,即是单台服务所能承载的并发用户数。当然,如果你对响应时间的要求不太高,也可以根据某个特定响应时间,比如200ms,来确定并发用户数。而不是非得用响应时间的增长拐点。

如下图所示:

用户在达到100左右开始,响应时间开始变高,QPS也不再随着VU线性增长。所以我们可以说对于这个服务节点来说,最好只用来支持100个用户。但是你也可以发现,即使到200并发用户,系统也还是OK的。只是延迟比较高。

这里的压力测试中使用的假设的思考时间的长短,是非常影响并发用户数测算的。如果我们把思考时间加到500ms,就可以支持更多的VU,因为QPS一般是固定的。下图展示了600VU在500ms思考时间下的测算结果。是不是看上去比上面100用户的时候还好?(注意,QPS都是1000左右。)

发表评论