性能测试总结
2017 年 5 月,我们针对半米主 app 进行了目标为 30 万 日活跃量的后端性能测试,所以记录一下测试的准备、实施、调优过程。(具体的业务和数据,不希望在文章中公开,都放在了公司内部文档服务器-> 作者的个人文件夹中 ^^)
背景:
当前活跃量:1.6 万/天
目标活跃量:30 万/天
结论:30W 日活时性能稳定;60W 日活时服务偶尔有性能抖动,短时间内可恢复,待优化。
准备阶段
上面已经说了这次的目标是支撑 30 万日活,准备阶段我们提出了如下几个问题:
- 如何搭建测试环境
- 如何确定测试目标接口、参数集合、并发量等指标
- 如何准备测试用假数据
- 如何执行测试请求
- 评估结果并找出可能的性能瓶颈
下面我来描述一下这四个问题是如何提出的,以及为什么会提出这些问题。如果各位看官想直接了解本次性能测试中的解决方法,可跳过**“提出问题”这一小节,直接阅读“解决问题”**部分。
一、提出问题
1.如何搭建测试环境
想要做性能测试,首先需要搭建一个与线上生产环境功能相同、又完全独立的测试环境,目的是为了避免性能测试时所带来的服务不稳定会影响线上服务。
2.如何确定测试目标接口、参数集合、并发量等
性能测试的基本思路是希望在特定目标日活跃量的条件下,模拟用户向后端服务发起的请求,并观察服务是否可用、稳定、性能良好,其中目标接口、参数集合、并发量这几个条件是我们比较关注的,因此我们先描述一下这几个概念:
目标接口:一个应用可能拥有上百个接口,其中有一些接口请求量很大(如:应用首页的请求),有一些则很小(如支付接口-_-)。需要挑选请求量大的接口,而不是全部接口去做测试。目的是有重点地测试,从而节省准备时间。
参数集合:在发送测试请求时,针对同一个接口,多次请求间需要使用不同的输入参数,目的是为了避免相同参数每次都能命中应用内缓存或数据库缓存,导致与真实场景下的请求模式不一致,性能测试结果出现偏差。因此我们需要构造一个请求参数的集合,去模拟多用户同时访问的情景。
并发量:并发量是指针对某一个接口,以什么样的频率发送请求、以及同时发送多少个请求。这个指标是根据目标日活跃量测算出来的(本文下一节将介绍如何测算这个指标),它直接影响了性能测试的结果。
3.如何准备测试用假数据
由于目标日活跃量远大于当前活跃量,一个不得不考虑的因素是:当日活达到目标量时,数据库内的数据量也定会远超过当前量级,那时的数据查询和写入性能将不及现在,如果我们还使用当前数据库备份进行测试,将带来结论的偏差。
考虑到这一点,我们需要向数据库部分数据表中填入大量仿造的数据,以达到预想的数据量级。
4.如何执行请求
执行请求过程,需要模拟客户端,定时定量地发送一批又一批的携带不同参数的请求,并记录它们的响应时间、成功率、实际并发量等指标。如何能简单快速实现这个功能是这次考虑的另一个问题。
5.评估结果并找出性能瓶颈
性能测试的最终目的是:
- 测算目前应用能否承受目标日活跃量的用户请求,并能保持稳定。
- 找到应用最大能承受的日活跃量上限,知道何时该考虑优化性能这件事情。
- 找出应用未来的性能瓶颈可能是哪里,日后优化时有章可循。
如何完成这些目标是我们最终关心的问题。
二、解决问题
本节对上一节提出的问题给出解决方案,这些方案不一定是唯一答案,只是在本次性能测试中采用的。
1.搭建测试环境
半米主 app 目前的应用部署结构是这样的:
注意:在实际生产环境中,为了实现高可用,peanut_baby
这个应用是部署在多台服务器上的多实例结构。而 ServerPeanut
和 uis
这两个应用因为业务复杂,暂时还是单个实例部署。
搞清楚了应用部署结构,我们在搭建测试环境时,就需要完整地复制出这样一套系统,具体分为如下几步:
- 购买两个 ECS 云服务器实例(按量付费,用完即可释放,注意留下磁盘镜像),一个 1 核 1G 用于部署 nginx 代理,另一个 2 核 4G 用于部署上述三个应用(由于目前 MongoDB 和 memcached 两个系统也是我们自己部署的,没有使用云服务实例,所以也部署在这台机器上)。
- 购买一个 RDS 云数据库实例(同样按量付费),配置为 1 核 1G,尽量与当前使用的 4 核 600M 的配置相似,并将线上用到的
peanut
和uis
两个数据库迁移至新实例。 - 部署这些应用时,注意修改它们的配置文件中各服务的地址以及端口,包括数据库的地址,全部从线上地址改为测试地址。
- 最后,为测试环境准备了一个新的域名,配置在 nginx 当中,并将该域名下的请求转发至测试应用服务器。
- 经过简单的接口调用测试,证实测试环境已全部搭建完毕
2. 确定测试目标接口、参数集合、并发量
目标接口:
- 为了让测试过程尽量真实,我们先对线上应用的访问日志进行了过滤,挑选出某一天请求高峰期(20:00-21:00)的半米主 app 请求,并按接口进行分类并计数,统计出请求量排名前 30 的请求列表。
- 由于应用正处在改版阶段,后端部分接口也存在未来弃用的情况,因此我们对某些目标接口进行了替换,如旧版首页接口在下一个版本将不再提供服务,因此我们将它替换为了与其对应的新版首页接口,这样的情况不止一个。
参数集合
- 针对每个请求,我们要结合业务逻辑,分析出不同用户发来的请求间,可能需要发生变化的参数组合,并将它们随机的生成出来。(如:首页会根据不同用户 id,返回该用户的个性化数据,因此在此接口处,
userid
将是一个需要参数化的点) - 对于一些不容易构造的请求参数,如:验证用的签名 sig,可以通过修改后端验证逻辑,对所有请求放行的办法,避免了验证时发生错误而阻止后续业务逻辑执行的情况发生。注意:这里一定不能删除验证过程,这相当于减少了接口的计算量,会造成测试结果不准确。
并发量
- 在测算并发量时,我们先将上述一小时内的真实访问量进行计数,并将该数字乘以 20(当前日活量与目标量的倍数差距),再将其换算成每秒请求量这样一个指标。
- 每秒请求量这个指标,实际上是 并发数✖️️ 步调频率 的乘积,经过简单的规划,就能得到合理的并发数和步调时间了。如:首页请求约每秒 45 个,我们将其规划成 5 个并发、步调频率每秒 9 次,也就是步调时间为 110 毫秒。
3.准备测试用假数据
- 方法很直接,结合业务逻辑,找出在目标日活的情况下,哪些数据表的数据量可能增长一个或多个量级,然后结合上文中构造的请求参数集合,向目标数据库中填充数据,比如:用户信息表,在用户量暴涨的情况下,数据量将会上一个量级,对它进行填充是必要的。
本次测试中,主要填充了
用户信息
相关的表和问答
相关的数据表。
4.如何执行请求
考虑到自己实现测试客户端,需要不小的成本,而一些常见的测试工具,又不能完全满足我们请求参数化的需求。因此,本次测试,使用了阿里云提供的性能测试服务,该服务支持测试脚本的参数化,以及对请求并发数和步调时间的定制,并可以统计测试结果的各项指标。
根据上文中确定的请求列表,我们在阿里云的性能测试功能中创建了 20 个不同请求的测试脚本,并上传参数集合文件(.csv 格式),完成参数化的定制。之后构建一个测试场景,将所有测试脚本加入进来,并设置测算好的并发数和步调时间,如下图所示:
注意:脚本命名规则有助于我们分辨请求的接口名称和请求量排名。
场景设置好后,我们就可以开始执行测试了。测试时间持续 1-2 个小时为好,不能太短。
5.评估结果
评估结果时,分两部分指标进行检查:
- 请求相关指标:各接口请求的平均响应时间,业务调用的成功率,实际达到的并发数是否与预期相符,这些都可以在阿里云性能测试监控页面看到,比较方便。这些指标直接表达了服务是否能达到目标要求。
- 服务器指标:要查看 nginx 服务器、应用服务器、数据库服务器的性能指标,比如:CPU 占用率、负载、IO 次数、内存使用情况等。这些指标间接反映了在服务出现问题时,可能的性能瓶颈会在哪个环节。
实施阶段
用较长的篇幅描述了测试的准备阶段,确实,准备阶段花费了我们不少的时间,当真正开始测试后,似乎轻松了一点。
- 打开阿里云的性能测试实时监控页面,查看实时收集到的请求结果统计
- 打开 zabbix 查看两台服务器的状态
- 打开阿里云 RDS 监控与报警页面查看
Mysql
数据库的各项指标。
验证结果阶段
测试跑完后,我们首先按请求量查看各接口平均响应时间是否足够快,错误率是否够低,并发数是否达到目标值。
以前的一个经验数据是:排名前十的接口,平均响应时间应该在 100ms 以内,错误率 0.1% 以内。
另外,整个测试时间段内,服务是否一直保持稳定,是否有性能的抖动,出现服务短暂不可用的情况。如果有这种情况,也需要分析其原因是什么,寻求改进。
这次测试中,我们分别对 30 万日活和 60 万日活两个目标进行了测试,测试的部分数据结果如下:
30W 日活
请求相关指标
请求成功率接近 100%,响应时间大部分都在 100ms 以下,部分问答业务相关的接口响应时间偶尔高于 100ms,如图:
请求的实际并发数与预期相同。
服务器指标
测试期间,Nginx 服务器负载和 CPU 占用都较小(使用了 https 连接),应用服务器的 CPU 占用在 30%-40% 左右,且负载最多在 0.3 左右,性能良好。
数据库服务器的 CPU 使用率在 20%,IOPS 为 45,连接数 10 以内。每秒 SQL 语句执行次数为 1200+。
每秒增删改查的次数为:
综上可见,在请求量在 30W 左右日活时,服务性能比较良好,可稳定提供服务。
60W 日活
构造 60w 日活的方法就是在 30w 的基础上,将并发量加倍,而其它指标不变,再次进行测试,这次进行了两个小时的测试。
请求相关指标
这次测试指标都还接近预期值,平均响应时间比 30W 日活时略有升高。
但在测试进行到 1 小时 42 分钟时,出现了短暂的响应超时、并发数下降的情况,如图:
可以看到首页请求的 TPS 在 19 点 42 分时骤降,其它请求也基本如此。
服务器指标
nginx 服务器的性能良好。
应用服务器负载和 CPU 都出现过短暂的过高,如图:
数据库服务器每秒 3400 个 SQL 语句执行,增删改查数目在:
CPU 占用最高达到 70%,IOPS 达到过 330/1500,连接数 30+
综上可见,在 60W 日活的情况下,应用服务器和数据库服务器都经受了比较大的压力,其中出现的短暂服务不可用的现象,需要解决。
总结
以上数据比较凌乱,这里总结了一个表格:
指标名称/用户量 | 30W | 60W |
---|---|---|
Nginx 服务器负载/CPU | 很低 | CPU 10+% |
应用服务器负载 | 平均 0.2 | 平均 0.5,偶尔超过 2 |
应用服务器 CPU 使用率 | 平均 35% | 平均 65%,偶尔爆表 |
数据库 CPU 使用率 | 平均 20% | 最高 70% |
数据库 IOPS | 45 | 最高 330 |
数据库连接数 | 10 以内 | 最高 30+ |
请求成功率 | 基本 100% | 偶有波动,表现为请求超时,1-2min 后自动恢复 |
请求平均响应时间 | 平均 100ms 以内 | 少部分请求超过 100ms |
实际并发数 | 与预期相符 | 与预期相符,除去服务不稳定的情况 |
调优&再次测试
根据测试结果,我们需要全面分析其中发现的问题,并准确找到限制性能的瓶颈,优化它,然后再次测试直到指标正常。在这里我举几个本次测试中实施过的和将要实施的优化手段。
- 数据库查询慢,添加索引:一般当某个请求响应很慢时,很容易想到是否是这个请求是否进行了很慢的数据库操作导致的,在本次测试中,通过跟踪慢响应请求的业务逻辑,找到了不止一处没有合理添加数据库索引的位置,加上后优化效果明显。
- CPU 占用高,扩充机器或升级配置:对于 60W 日活时,出现的应用服务器 CPU 占用偶尔爆表的情况,考虑升级该服务器的配置或扩充更多的应用服务器并采用负载均衡策略是可选的解决办法。当然如果能够找出应用内计算量较大的过程,并思考优化办法也是一种思路。
- 数据库性能遇到瓶颈:对于 60W 日活时,数据库服务器 CPU 已达 70% 使用率,算是较高的水平,仍然可以考虑升级该机器的配置。其它一些优化数据库的手段包括分库分表、读写分离等,作者还没有使用过,暂不多说。
遇到的问题
这章说一些本次测试中遇到的问题,算是替小伙伴们先踩了几个坑了。
请求失败,TLS 版本问题
由于本次测试都是 https 的请求,一次测试过程中,发现测试开始的几分钟后,请求全部失败,报错为 SSLHandShakeException,经过调查和询问阿里云的客服,发现是 TLS 协议的问题,在客户端指定使用 TLSv1.2 协议将会解决这个问题,具体:
在测试脚本中加入这两句话即可,其它类似方案读者请自行查找。
from java.lang import System
System.setProperty("https.protocols", "TLSv1.2")
周期性请求超时,Linux 系统的 TCP 连接数限制
测试过程中层出现周期性请求超时,查看服务器系统日志,发现如下错误:
/var/log/message错误:
May 20 16:05:16 iZbp1iilv2rcpfyeufid9cZ kernel: nf_conntrack: table full, dropping packet.
May 20 16:12:15 iZbp1exxqofq686dq3q16kZ kernel: TCP: time wait bucket table overflow
富有经验的总工找出了这个问题所在,并修改了几个相关的系统参数,解决了问题:
/etc/sysctl.conf
net.netfilter.nf_conntrack_max = 318720
net.nf_conntrack_max = 318720
net.ipv4.tcp_max_tw_buckets = 500000
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于