关于性能测试

性能测试已经是一个老生常谈的话题了,不同的项目或多或少都会涉及到,但是每个人的经验肯定有所不同。今天我想从以下几个方面分享一下我认为关于性能测试需要重视的要点。

1. 性能测试的需求

看似很明确的需求

当问到性能测试的需求,我们的客户有时候会回答,“我们要支持20000个用户同时在线”,或者“每天有10000个用户同时访问这个网站”,更有甚者“我们也不知道,越多越好吧”。这种情况下我们应该怎么办呢?

当用户说“我们有10000个用户同时访问这个网站”的时候,看似我们的需求已经很“明确”了。真的很明确了吗?就拿这个例子来说,这10000个用户都在干什么呢? 假想下,可能有5000个用户在浏览静态页面,2000个在创建表单,1000个在做查询,还有… 真正产生压力的用户是谁呢?这恐怕不能单单从10000个用户同时访问这个网站得到结果。

之前做过一个项目,客户为某机场,他们为用户提供免费的WiFi服务,但是提供在WiFi的同时,会在不同的页面播放广告。后台有一个广告管理系统,可以做一些配置,根据优先级来投放广告。这其中涉及到一些复杂的算法,主要是投放广告的比例。当然机场还有很多物料服务器,主要是用来存放广告的图片等等。当时用户给的性能需求也就一句话,“我们每天大概有xxxxxx个用户连接我们的WiFi”。那怎样来做性能测试呢?

“多少个用户连接WiFi”其实只是一个最表面的现象,在连接WiFi的同时,其实系统做了很多事情。首先连接WiFi的时候会去访问分发服务器,确定要播放哪几个广告,其次会根据分发的广告,去物料服务器上找相应的资源,然后打点把广告访问的历史数据记录到数据库中。这是能联想到的一些最基本的后台操作,每一个行为都有可能成为一个瓶颈,都需要去认真考虑。

所以在考虑性能需求的时候,一定要清楚背后的逻辑是什么,同时需要分析用户活动,从而确认系统的性能测试目标。

历史数据

“性能现在的需求也不是很明确,但我们有原来的老系统,能帮我们看下吗?”

对于性能测试来说,如果有很详细的历史数据,这将是一个非常珍贵的数据源,我们可以从中分析出很详细用户行为。之前我们做过一个项目,性能测试的需求基本上就是从历史数据中得到的。我们对数据库进行了很详细的分析,从而设计性能测试用例,这其中主要包含系统的哪些service承受了压力、压力是多大、用户量是多大等等。

需要特别注意的是,如果对于业务的了解不够深,很容易导致场景的设计缺失,从而导致测试的场景和实际的场景有所偏差。 比如分析某张表,分析出系统有10000个插卡拔卡记录,有的人可能直接就开始写脚本了,模拟一个用户每天做10000次插卡拔卡的操作。 殊不知,这10000次插卡和拔卡的操作是4000个用户完成的…而性能的瓶颈很有可能就在用户的管理,而不是插卡拔卡产生的压力。

所以建议在做历史数据分析的时候,一定尽量把场景分析全面,并且需要去了解真正的业务,才可以尽量减少分析的错误。

不明确的需求

“我们也不知道需求是什么,你们看着办吧。”

这种情况通常出现在一个新的项目,客户对上线后的用户体量并没有很好的认知。我们应该怎么做呢?

通常我们会对系统进行负载测试,就是在被测系统上不断增加压力,直到性能指标(如响应时间)超过预期或者某种资源已经快达到饱和状态,这个时候我们基本就可以确定系统的瓶颈是什么、支持多大的吞吐量。再通过一段时间的稳定性测试,让系统在一定的时间内(比如一周)维持一定的压力,去查看相应的性能指标。

这样,我们就可以告诉我们的用户,系统现在支持多大的用户访问量、吞吐量是多少。

当然,现在提到的都是客户的需求,对于性能测试本身来说,我们也有一些明确的需求,也就是我们通常提到的index,下面我会继续深入讨论这个问题。

2. 定义性能测试的指标

性能测试指标

通常来说我们会关注如下的性能测试指标。

响应时间:比较熟悉的就是2-5-8原则(据统计当网站慢一秒就会流失十分之一的客户),通常来说,2到5秒,页面体验会比较好,5到8秒还可以接受,8秒以上基本就很难接受了。但是有的项目也会例外,比如从海量的数据中去查询某些数据,或者生成报告(年度报告),这种可能就不太适合2-5-8原则,但是前提是要管理好客户的期望。

吞吐量:指的是在单位时间内客户端和服务器成功传送数据的数量

并发:客户/服务端同一批用户同时执行一个操作的数量

资源使用率:通常来说,我们关注的资源就是几大块:内存、CPU、I/O和网络

成功率:比如在某些情况下,API调用的成功率

当了解了我们所需要关注的性能指标,就可以来定义具体的性能测试指标了。

我之前做一个比较大的项目的性能测试,系统提供很多服务,有web service,有windows的services,基本上每个services都是单独部署在一台window的服务器上,所有的services都连到同一个数据库。由于这个系统并不存在网页,所以我们并没有把页面加载这些考虑在内。我们从历史数据中首先了解到了系统需要满足的并发量,针对每个service去做相应的压力测试,并且定义对应的性能测试指标。如下图:

  1. 上图就是对于web service性能测试的指标和结果,这个结果是我们基于每秒有7个Post请求+3个get请求而产生的。
  2. 这其中的性能指标包含了我们对这个service的单独的内存监控、CPU监控,以及成功率的监控,还有相应的原因分析。 通常来说,对于内存、CPU等的占用率,业界都有很共识的标准,大家可以拿来作为参考,由于我们服务器是8g内存,8核CPU,所以表现还是相当不错的。
  3. 其他的service也是类似的,我们考察的点主要在于CPU,内存的占用率,还有成功率。
  4. 由于网络一开始我们就认为不是瓶颈(都在内网),所以我们并没有把网络相应的指标加进去。
  5. 需要特别注意的数据库server,和其他windows service或者web service不一样,对于DB server我们需要特别关注的是I/O,数据库的瓶颈一般都是出现在I/O上面。

自定义指标

在项目中我们往往还要自定义一些其他的指标,来查看某些特定的性能。比如:

为了分析方便,我们在一些service的关键方法上都加上时间戳,便于去做trouble shooting。于是我们有了一下的指标,图一是某个方法的平均调用时间,图二是我们计算出的最慢调用方法的top10:

总体来说,性能测试的指标可以从不同的方面去定义,比如响应时间、资源利用、并发数、吞吐量、并发数…

3. 如何trouble shooting

刚才提到了性能测试中的一些相应的指标,当我们发现某些指标不正常的时候,我们应该怎样去做呢?

性能测试的指标往往又是相互关联的,当遇到问题时,我们需要从这些指标的结果中去寻找真正的root cause,这也是性能测试最重要的点之一。废话少说,我们直接来看几个例子吧:

内存泄露

内存泄露是性能测试中很常见的一个点,比如下图,很明显能看到有一个内存上升的趋势,这种情况下很可能就有内存泄露的情况。 但是也有例外,比如是C#或者Java写的代码,因为内存回收是垃圾回收器自己的行为,不像C语言那样需要手动回收,所以即使内存在一段时间内有上升的趋势,但是只要能回到原点,就不存在内存泄露的现象。

内存泄露很多时候并没有很明显的征兆,有一次客户抱怨服务挂掉了,这个服务之前有好几个月都运行的好好的,怎么说挂就挂呢,然后我们对系统日志做了很详细的分析,发现就是内存泄露惹的祸。每天内存泄露一点点,到第三个月的时候才导致服务崩溃,这在短期的性能测试中就很难发现。

下图是当时我们在做性能测试的时候发现的问题,很明显内存在一段时间内直接从200多MB升到了500多MB,很有可能存在内存泄露的情况。怎样trouble shoooting呢?

我们借助了第三方的工具ANTS,最终有下面的结果,很明显Sting类变成了最大的类,高达211MB,这个是什么原因造成的呢?然后我们在工具上找了String object的调用链,最终发现代码中有一行的判断错误,导致了内存上升。

API响应速度慢

这是一个很常见的问题,单单看现象,其实我们并不知道系统是哪里出的问题,有可能是网络慢,有可能是内存出现了瓶颈,也有可能是I/O的问题… 怎样定位呢?

首先我们还是找到相应的apiserver,通过性能测试的报告,我们发现了下面的问题,message queue随着时间的变化增长特别快,这个时候看API server本身的内存和CPU并没有什么问题,那么问题到底处在哪呢?

这个时候其实很容易就联想到是数据库出现了问题,对比两个不同的版本,发现我们对数据库的存储过程有所更改,最终发现是存储过程的更改导致了某个索引失效,最终反映到API就是速度明显变慢。添加索引后问题得到了解决。一般来说,当我们在修改数据库的时候一定要特别小心,很可能一个小的改动就会导致系统性能的急剧下降。

对于性能测试来说,往往我们发现的问题都是表象上的问题,比如页面反应很慢。但这有可能是多种原因导致的,需要去做深入的分析。这也要求我们在做性能测试时去收集足够的信息,以支撑分析,同时借助一些第三方工具,才能真正定位到问题。

4. 关于数据库

其实性能测试我前面已经提到了很多,为什么还要把数据库单拿出来说呢?我们来看看数据库服务器的特殊之处:

关于内存

我们在进行服务器的内存的测试的时候,如果是普通的APP server,当我们对内存进行监控时,通常都是有一个metrics, 比如某个APP的service的内存使用量不能超过总量的百分之多少,对于整个sever来说,通常也会有比较多的内存剩余。

而当我们去查看一个数据库server的时候,会发现可用的内存量通常只有10M或者5M(取决于你的配置)…… 而当你把数据库的内存增加一倍,可用的内存通常还是只有10M左右,这是什么原因呢?

其实这与数据库本身的工作原理有关系,数据库中io操作的基本单位为页,当数据库执行一条语句,比如一条查询语句,它会先从物理磁盘中把相应的页加载到内存,然后再进行操作。

因为数据库本身就在不停的读写,所以数据库内存当中会缓存各种各样的数据,为了更快的读写,数据库会有算法去维护这些内存中的数据,以保证尽可能的使得数据都是从内存中获得,而不是从物理内存中得到(内存的访问速度是纳秒级,硬盘是微秒级)。所以一般来说,数据库会基本用光所有的内存。

这就是为什么数据库会吃内存了,这与其工作原理有关系,只要能保证系统能正常运行就可以了,其他的内存都可以用来缓存数据。

关于死锁

死锁通常是指争抢资源不当,让双方因为对方掌握了自己的资源而无限期的等下去。如下图所示:

发生死锁的原因很多,大部分是由于事务之间对资源访问顺序的交替,或者并发修改统一记录导致的,数据库对待死锁有着不同的策略,对于SQL server来说,它会随机杀掉其中的一个,至少保证另外一个事务的正常运行;而对于Oracle来说,它会对两个事务进行一个评估,会杀掉它认为不那么重要的一个。

所以我们在数据层面,需要去监控死锁的发生情况,一般来说,死锁是不可避免的,但是一旦死锁发生频率很多,必定会影响到业务。

我们可以用很多工具去监控死锁,比如SQL Profile。对于死锁的修正也是对于高并发的事务,尽量减少长度;把锁的优先级调整低一些(用低隔离级别);按同一顺序访问对象,尽量避免事务中的用户交互。

关于磁盘

因为对于数据库来说,最重要的就是读写的操作,磁盘对于数据库来说是非常重要的。

现在好多数据库都是直接放在云盘上的,云盘上的磁盘有着自己的结构,但是很多年前,如果是自己的服务器,我们还要考虑到磁盘阵列,来保证磁盘的效率和安全性。

对于磁盘来说,我们通常会从读写方面来衡量,有几个比较通过的指标可以用来衡量数据库磁盘的性能,比如Average Disk queue lenth,数据生命周期等等。

另外值得一提的是数据库的碎片整理。当数据库读写一段时间之后,由于频繁的插入数据,会导致数据并不是按照顺序排列在磁盘上面,这样当我们在查相关数据的时候,磁盘往往要去不同的区域查找数据,导致性能降低,这个时候我们很有必要去运行磁盘的碎片整理,来保证数据库性能。

但是这个job本身就很占磁盘的I/O,所以尽量选择系统不忙的时候进行。

关于索引

相信索引是大家很熟悉的一个话题了,当数据量很小时,不建索引,进行全表扫描的的性能尚可接受。但是数据量大时,必须借助索引。所以索引的适当与否,是性能好坏的关键。

比如执行一条语句

SELECT UserName, UserID  FROM US.UserDetail WHERE UserName = “李四”

这条语句这样执行与创建的索引有关系,如果没有索引就需要进行全表扫描,这样加载到内存当中的页在数据量很大的情况下就相当多了。如果建立了适当的索引,可能只需要加载几页内存就可以了。

当我们用一些工具比如sql profile跟踪到一些长的查询的时候,我们就需要去看看索引是否建立恰当。这在数据库的优化中也是很重要的一个方面。当然,索引虽然对读的性能有帮助,但是对写的性能却有影响。

我们也需要再适当的时候reindex索引,原因就是如果索引在物理存储上不连续,也会导致性能的下降,这与磁盘的碎片整理是一个道理。

一般来说,如果系统设计合理,最终的瓶颈都会出现在数据库上,而我们在做性能测试时,也需要了解数据库的特殊之处,去更好的做性能测试。

5. 关于性能测试的工具

对于市面上的那么多性能测试工具,怎样选择呢?

性能测试的工具实际上有很多,能列出来的比如Jmeter,Loadrunner,Galtling等等,这取决于很多方面:

  1. 需要测试测试服务端的性能?还是前端的性能?或者是mobile端的性能?后端的话可以选择Jmeter、Loadrunner或者Gltling。前端的话可以选择比如Fiddler、httpWatch等,手机端可以选用GT或者APT等。
  2. 项目的成本,很多性能测试的工具是收费的,也有很多是开源的,需要结合具体的项目进行考虑。
  3. 我们也可以自己写脚本来完成性能测试,只要能满足要求项目的要求就可以。但是通常来说使用已有的工具一般会产生比较好的测试报告,除非一些特殊情况,比如某些协议的特殊性,或者很简单的一些场景等,一般来说还是建议使用市面上已有的工具。
  4. 使用工具的同时,尽量保证测试系统和测试的脚本在同一个网络里面,这样测试出来的结果才有意义。

总的来说,性能测试其实是一个很复杂的过程。需要结合不同的方面正确分析需求,根据需求设计出合理的测试场景,定义出性能测试的指标,并且在测试中选择合适的工具进行测试,并且结合测试的多项结果进行分析和trouble shooting。

Share

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据