性能测试的思考(参考阿里PTS)
线上业务压测的核心要素
做到 5 个一样
要达成精准衡量业务承接能力的目标,业务压测就需要做到 5 个一样:
- 一样的线上环境
- 一样的用户规模
- 一样的业务场景
- 一样的业务量级
- 一样的流量来源
其实这样的条件,基本上在中小型公司是难以到达的条件。
总结出业务压测的核心要素是:
- 压测环境
- 压测基础数据
- 压测流量(模型、数据)
- 流量发起、掌控
- 问题定位
Gor
录制线上请求,序列化成Json String
, 持久化到redis
;流量发起可以借助于Jmeter
或者Gatling
、Loadrunner
来产生或者也可以使用使用TCPCopy
直接放大线上流量(开发和运维操作);最后问题定位一般通过监控系统观察错误和响应时间或者通过代码打点统计日志数据,当然也可以通过自建的ELK
日志分析和配合Grafana
来监控服务器来排查问题,如果需要方便进行端到端的全监控和问题排查可以引入APM
这样的系统。关于压测环境和压测基础数据 结合起来一起讲:
全新生产环境。如果是刚迁移到云上或者是新的机房,全链路的进行业务压力测试之后可以进行正式投产的,这种验证效果较好,因为最终就是真实的性能环境,一般可以将真实的生产环境数据进行脱敏导入,确保业务数据量(交易数据、流水、各种业务核心业务记录等)维持在半年以上,同时确保数据的关联完整性(包括跨系统的业务完整性数据),压测基于这些基础数据进行相应的核心业务的流量(登陆、购物车行为、交易行为等)构建,最后在投产前做相应的数据清理再初始化一次存量基础数据。
等比性能环境。一般是指在生产环境单独划分区域,准备等比的容量,共享接入层的性能测试环境。这种方案缺点是成本较高,优点是方案简单、风险可控,容量规划较为精准。其中需要注意的几个点是,1必须保证有共享的接入层(CDN动态加速、BGP、WAF、SLB、4层7层负载均衡等等,确保最重要的网络接入层相同,能发现问题);2是后端的服务容量配比上至少保证是生产环境的1/4,配比越大精准度也会大幅下降,数据库建议能相同配置。最后,在基础数据的准备上和上面全新生产环境的方法一致。
生产环境。生产环境上基础数据基本分为两种方式,一种是数据库层面不需要做改造,直接基于基础表里的测试账户(相关的数据完整性也要具备)进行,压测之后将相关的测试产生的流水数据清除(清除的方式可以固化sql脚本或者落在系统上);另一种就是压测流量单独打标(如单独定义的Header),然后业务处理过程中识别这个标并传递下去,包括异步消息和中间件,最终落到数据库的影子表或者影子库中,一般称之为全链路压测。此外,生产环境的压测尽量在业务低峰期进行从而避免影响生产的业务,无论上述哪种方式都可以通过部署单独的压测专用集群来进一步避免对生产业务的影响。
关于业务的挡板 一般生产环境的业务压测还会涉及到和第三方的交互,如短信、支付和渠道对接等等,往往需要mock掉。最后,关于方案的选择应该结合实际的情况如人力资源、机器资源、时间成本、业务复杂度、业务要求和后续的维护成本综合考虑最适合自身的方案。
理想的性能测试方案, 至少满足如下几个场景
- 可持续集成,也就是固定的服务在部署到特定的环境后可以自动使用老的数据回归性能。
- 监控平台化,可以随时调出待测服务器集群中的某台设备查看TPS/QPS和CPU 句柄数 连接数 流量 JVM的profile数据 数据库的状态数据 。
- 应用自身log统计数据的对应关系图,可以随时调出上个季度的服务器性能和当前的服务器性能做对比。
- 问题定位方便,可随时根据时间点和曲线图的拐点分析特定时间段的各类指标和应用log
- 基准测试(Standard Testing)
- 负载测试(Load Testing)
- 压力测试(Stress Testing)
- 疲劳强度测试
负载测试,主要指的是模拟系统在正常负载压力场景下,考察系统的性能指标。这里说的正常负载,主要是指用户对系统能承受的最大业务负载量的期望值,即预计系统最大应该支持多大用户的并发量。通过负载测试,目的是验证系统是否能满足预期的业务压力场景。
和负载测试的概念比较接近的是压力测试。通俗地讲,压力测试是为了发现在多大并发压力下系统的性能会变得不可接受,或者出现性能拐点(崩溃)的情况。在加压策略上,压力测试会对被测系统逐步加压,在加压的过程中考察系统性能指标的走势情况,最终找出系统在出现性能拐点时的并发用户数,也就是系统支持的最大并发用户数。
【性能测试的思考(参考阿里PTS)】最后再说下疲劳强度测试。其实疲劳强度测试的加压策略跟负载测试也很接近,都是对系统模拟出系统能承受的最大业务负载量,差异在于,疲劳强度测试更关注系统在长时间运行情况下系统性能指标的变化情况,例如,系统在运行一段时间后,是否会出现事务处理失败、响应时间增长、业务吞吐量降低、CPU/内存资源增长等问题。
通过对比可以发现,不同的性能测试类型,其本质的差异还是在加压策略上,而采用何种加压策略,就取决于我们实际的测试目的,即期望通过性能测试发现什么问题。明白了这一点,性能测试类型的差异也就不再容易混淆了。
结论要点1:性能测试手段的重点在于加压的方式和策略。性能瓶颈定位的核心 在前面频繁地提到了性能指标,那性能指标究竟有哪些,我们在性能测试的过程中需要重点关注哪些指标项呢?
从维度上划分,性能指标主要分为两大类,分别是业务性能指标和系统资源性能指标。
业务性能指标可以直观地反映被测系统的实际性能状况,常用的指标项有:
- 并发用户数
- 事务吞吐率(TPS/RPS)
- 事务平均响应时间
- 事务成功率
- 服务器:CPU利用率、处理器队列长度、内存利用率、内存交换页面数、磁盘IO状态、网卡带宽使用情况等;
- 数据库:数据库连接数、数据库读写响应时长、数据库读写吞吐量等;
- 网络:网络吞吐量、网络带宽、网络缓冲池大小;
- 缓存(Redis):静态资源缓存命中率、动态数据缓存命中率、缓存吞吐量等;
- 测试设备(压力发生器):CPU利用率、处理器队列长度、内存利用率、内存交换页面数、磁盘IO状态、网卡带宽使用情况等。
System\Processor Queue Length
,而在Linux系统中则需要看load averages
。TPS模式(吞吐量模式)是一种更好的方式衡量服务端系统的能力。TPS获取新系统:没有历史数据作参考,只能通过业务部门进行评估。旧系统:对于已经上线的系统,可以选取高峰时刻,在5分钟或10分钟内,获取系统每笔交易的业务量和总业务量,按照单位时间内完成的笔数计算出TPS,即业务笔数/单位时间(560或1060)
系统的性能由TPS决定,跟并发用户数没有多大关系。
系统的最大TPS是一定的(在一个范围内),但并发用户数不一定,可以调整。
可能对于最后一项(测试设备)有些人不大理解,监控被测系统环境的相关硬件资源使用情况不就好了么,为什么还要关注测试设备本身呢?这是因为测试设备在模拟高并发请求的过程中,设备本身也会存在较高的资源消耗,例如CPU、内存、网卡带宽吃满,磁盘IO读写频繁,处理器排队严重等;当出现这类情况后,测试设备本身就会出现瓶颈,无法产生预期的并发压力,从而我们测试得到的数据也就不具有可参考性了。此处暂不进行展开,后面我会再结合实际案例,通过图表和数据对此详细进行说明。
需要说明的是,性能指标之间通常都是有密切关联的,单纯地看某个指标往往很难定位出性能瓶颈,这需要我们对各项性能指标的含义了然于胸,然后才能在实际测试的过程中对系统性能状况综合进行分析,找出整个系统真正的瓶颈。举个简单的例子,压力测试时发现服务器端CPU利用率非常高,那这个能说明什么问题呢?是服务端应用程序的算法问题,还是服务器硬件资源配置跟不上呢?光看这一个指标并不能定位出产生问题的真正原因,而如果仅因为这一点,就决定直接去优化程序算法或者升级服务器配置,最后也很难真正地解决问题。
结论要点2:性能瓶颈定位的重点在于性能指标的监控和分析。压测场景的结构和数据分配 基本概念
- 压测 API:指由用户行为触发的一条端上请求,是压测中的必需元素。
- 串联链路:指一组压测 API 的有序集合(类似于事务),具有业务含义。
- 数据导出指令:用于导出某个串联链路中的数据(导出 Cookie 为典型应用),供其他串联链路使用,实现导出数据的全局共享。
- 关联数据文件:压测 API使用了来自数据文件的参数,从而关联了相应的数据文件。如果使用了多个文件参数分别来自于不同数据文件则表示关联了多个数据文件。
- 断言:一般用于标记业务成功与否,从而验证压测请求的响应是否符合预期。
- 数据轮询一次:在请求中使用文件参数时,数据文件只轮询一次,以保证请求信息不重复。
- 出参:在创建串联链路时,将前置接口的部分返回信息作为参数。
下图中串联链路1 的 API1 是登录业务相关接口,其典型配置如下图:
image.png 图例说明如下:
- 数据导出指令一般应用于登录之后需要并行压测多个不同业务串联链路的情况,支持标准的 Cookie 导出或者是业务自定义的 出参 导出。
- 串联链路1 使用了数据导出指令,数据导出完成后其他串联链路才能开始压测,所以与其他串联链路不是并行的关系。
说明:只有当使用了数据导出指令才会出现串联链路之间不是全都并行的情况。
- 为保证用户登录信息不重复,需设置压测 API 数据只轮询一次。串联链路1 中 API1 设置了数据只轮询一次。
- 一批用户登录完成后,将用户登录信息共享给场景内其他业务的串联链路使用,需设置数据导出的准备量级。达到该量级才会触发场景内剩余串联链路进行压测(它们彼此间还是并行的,数据分配逻辑和上面所述一致)。
- 准备量级需要小于等于登录接口的文件行数。如图所示,关联数据文件为 200 行,导出量级设置为 100。
注意:由于目前任务是拆分给施压agent单独执行的,故可能出现准备量级并没有达到设定值便有其他串联链路开始压测的情况。引入性能测试工具 通过前面的讲解,我们已经知道性能测试的主要手段是通过产生模拟真实业务的压力对被测系统进行加压,与此同时监控被测系统的各项性能指标,研究被测系统在不同压力情况下的表现,找出其潜在的性能瓶颈。
那么,如何对系统进行加压,又如何对系统的指标进行监控呢?这里,就需要引入性能测试工具了。
当然,我们也可以先看下在不借助性能测试工具的情况下,如何手工地对系统进行性能测试。
假设现在我们要对前面提到的搜索功能进行负载测试,验证在20个并发用户下搜索功能的事务平均响应时间是否在3秒以内。
很自然地,我们可以想到测试的必要条件有如下几点:
- 20个测试人员,产生业务压力
- 1个指挥人员,对20个人员的协调控制,实现并发操作
- 1个结果记录人员,对每一个人员的操作耗时进行监控和记录
- 若干资源监控人员,实时查看被测系统的各项性能指标,对指标进行汇总、分析
- 1个结果统计人员,对20个用户各操作消耗的时长进行汇总,计算其平均值
性能测试工具的基本组成 当前,市面上已经有了很多性能测试工具,但不管是哪一款,基本都会包含如下几个核心的模块。
- 压力生成器(Virtual User Generator)
- 结果采集器(Result Collector)
- 负载控制器(Controller)
- 系统资源监控器(Monitor)
- 结果分析器(Analysis)
文章图片
Google Search 对照前面手工进行性能测试的案例,不难理解,压力发生器对应的是众多测试人员,结果采集器对应的是结果记录人员,负载控制器对应的是指挥人员,资源监控器对应的是若干资源监控人员,结果分析器对应的是结果统计人员。
其中,压力发生器又是性能测试工具最核心的部分,它主要有两个功能,一是真实模拟用户操作,二是模拟有效并发。
然而,大多数性能测试工作人员可能都会忽略的是,当前市面上性能测试工具的压力发生器基本都是存在缺陷的。
先说下模拟真实用户操作。如果熟悉浏览器的工作原理,就会知道浏览器在加载网页的时候,是同时并发多个TCP连接去请求页面对应的HTTP资源,包括HTML、JS、图片、CSS,当前流行的浏览器普遍会并发6-10个连接。然而,性能测试工具在模拟单个用户操作的时候,基本上都是单连接串行加载页面资源。产生的差异在于,假如页面有100个资源,每个HTTP请求的响应时间约为100毫秒,那么浏览器采用6个连接并行加载网页时大概会需要1.7秒(
100/6*100
毫秒),而测试工具采用单连接串行加载就需要10秒(100*100
毫秒),两者结果相差十分巨大。这也解释了为什么有时候我们通过性能测试工具测试得到的响应时间挺长,但是手动用浏览器加载网页时感觉挺快的原因。再说下有效并发。什么叫有效并发?有效并发就是我们在测试工具中设置了1000虚拟用户数,实际在服务器端就能产生1000并发压力。然而现实情况是,很多时候由于测试设备自身出现了性能瓶颈,压力发生器产生的并发压力远小于设定值,并且通常测试工具也不会将该问题暴露给测试人员;如果测试人员忽略了这个问题,以为测试得到的结果就是在设定并发压力下的结果,那么最终分析得出的结论也就跟实际情况大相径庭了。不过,我们可以通过保障测试环境不存在瓶颈,使得实际生成的并发压力尽可能地与设定值一致;另一方面,我们也可以通过在测试过程中监控Web层(例如Nginx)的连接数和请求数,查看实际达到服务器端的并发数是否跟我们的设定值一致,以此来反推压力发生器的压力是否有效。
了解这些缺陷的意义在于,我们可以更清楚测试工具的原理,从而更准确地理解测试结果的真实含义。
性能测试工具推荐 经过充分的理论铺垫,现在总算可以进入正题,开始讲解工具部分了。
在性能测试工具方面,我重点向大家推荐
Locust
这款开源工具。目前阶段,该款工具在国内的知名度还很低,大多数测试人员可能之前都没有接触过。为了便于理解,我先将Locust
与LoadRunner、Jmeter这类大众耳熟能详的性能测试工具进行简单对比。\ | LoadRunner | Jmeter | Locust | Gatling |
---|---|---|---|---|
授权方式 | 商业收费 | 开源免费 | 开源免费 | 开源免费 |
开发语言 | C/Java | Java | Python | Scala |
测试脚本形式 | C/Java | GUI | Python | Scala |
并发机制 | 进程/线程 | 线程 | 协程 | 协程 |
单机并发能力 | 低 | 低 | 高 | 高 |
分布式压力 | 支持 | 支持 | 支持 | 支持 |
资源监控 | 支持 | 不支持 | 不支持 | 不支持 |
报告与分析 | 完善 | 简单图表 | 简单图表 | 完善 |
支持二次开发 | 不支持 | 支持 | 支持 | 支持 |
从功能特性的角度来讲,LoadRunner是最全面的,用户群体也是最多的,相应的学习资料也最为丰富。个人建议如果是新接触性能测试,可以先熟悉LoadRunner,借此了解性能测试工具各个模块的概念和功能,在此基础上再转到别的测试工具,也都比较好上手了。不过,LoadRunner只能在Windows平台使用,并且并发效率比较低,单台压力机难以产生较高的并发能力,并且不符合现在DevOps的理念,现在大部分互联网企业也很少用到这样的重型武器了。
同样地,Jmeter的并发机制也是基于线程,并发效率存在同样的问题;另外,Jmeter在脚本编写和描述方面是基于GUI操作,也相对简单,Jmeter是一个轻量级的工具了,依赖于Java,并且社区也相对活跃,使用Jmeter的企业也有很多,容易进行二次开发。
Gatling,这是一款发布很久但在国内使用范围不是很广泛的压测工具。使用的是和Java同源的Scala这样的小众语言开发的,同时脚本也是基于Scala的。
Locust是一款Python的性能测试框架,非常小巧灵活,使用Python来进行编写脚本,因为采用协程模式,单机并发可以到达比较高,但是缺陷也比较明显,还没有形成一个完整的生态,很多多谢都需要自己进行二次开发。
采用多线程来模拟多用户时,线程数会随着并发数的增加而增加,而线程之间的切换是需要占用资源的,IO的阻塞和线程的sleep会不可避免的导致并发效率下降;正因如此,LoadRunner和Jmeter这类采用进程和线程的测试工具,都很难在单机上模拟出较高的并发压力。而协程和线程的区别在于,协程避免了系统级资源调度,由此大幅提高了性能。正常情况下,单台普通配置的测试机可以生产数千并发压力,这是LoadRunner和Jmeter都无法实现的。面对这样的情况可以考虑选择Locust和Gatling这样协程并发的工具来进而实现目标。
性能测试结果 我认为一个完整的性能测试结果应该有四部分构成,预期目标、测试脚本、测试结果、调优建议
预期目标 这个预期目标是我们做性能测试的前提,只有确定了预期目标,获得了大家的一直认可,以此为基准。
一般预期目标会是,
接口每天被5000个人调用,同时在线500人,每天要被调用50000次,这种话一般都是什么产品说出来的,因为,他们根本不管你什么原理不原理的,反正就是说,我的系统要怎么样怎么样,能做到多少支撑量。或者更加官方点会附带,我们系统需要的TPS是多少,在完成这样的TPS中,CPU和内存不能超过多少,在这样的前提下有支持多少用户的并发,这样听起来就比前面的好多了。
测试脚本 测试脚本就是,我们为了完成这个性能测试任务设计的脚本,让它来模拟线上用户的真实情况对服务器产生压力。
这是非常关键的一环,因为设计的脚本好坏,直接关系到最终的测试结果,
常见的设计思路是使用80/20原则来计算平均峰值来作为我们的指标。
80/20峰值公式:80%的业务是在20%的业务时间内完成的当然28原则也不能硬套,需要使用实际的生产数据来做出统计,有时候跟28原则差距还是很大的,甚至有可能是1:100
文章图片
image.png
真实的tps需要用底层的qps来计算,而业务应该承受的并发需要根据这个基础结合场景自己去推算.。
测试结果 这里一般就会使用到各种监控工具来,持续观察服务器的状态。
文章图片
image.png 这个监控工具看自己业务的实际情况而定。
调优 确定问题
应用程序代码:在通常情况下,很多程序的性能问题都是写出来的,因此对于发现瓶颈的模块,应该首先检查一下代码。数据库配置:经常引起整个系统运行缓慢,一些诸如大型数据库都是需要DBA进行正确的参数调整才能投产的。操作系统配置:不合理就可能引起系统瓶颈。硬件设置:硬盘速度、内存大小等都是容易引起瓶颈的原因,因此这些都是分析的重点。网络:网络负载过重导致网络冲突和网络延迟。
分析问题
当确定了问题之后,我们要明确这个问题影响的是响应时间吞吐量,还是其他问题?是多数用户还是少数用户遇到了问题?如果是少数用户,这几个用户与其它用户的操作有什么不用?系统资源监控的结果是否正常?CPU的使用是否到达极限?I/O 情况如何?问题是否集中在某一类模块中? 是客户端还是服务器出现问题? 系统硬件配置是否够用?实际负载是否超过了系统的负载能力? 是否未对系统进行优化?通过这些分析及一些与系统相关的问题,可以对系统瓶颈有更深入的了解,进而分析出真正的原因。
确定调整目标和解决方案
高系统吞吐量,缩短响应时间,更好地支持并发。
测试解决方案
对通过解决方案调优后的系统进行基准测试。(基准测试是指通过设计科学的测试方法、测试工具和测试系统,实现对一类测试对象的某项性能指标进行定量的和可对比的测试)。
分析调优结果
系统调优是否达到或者超出了预定目标?系统是整体性能得到了改善,还是以系统某部分性能来解决其他问题。调优是否可以结束了。最后,如果达到了预期目标,调优工作可以先告一段落。
调优注意事项
- 在应用系统的设计开发过程中,应始终把性能放在考虑的范围内,将性能测试常态化,日常化的内网的性能测试+定期的真实环境的业务性能测试。
- 确定清晰明确的性能目标是关键。
- 必须保证调优后的程序运行正确。
- 系统的性能更大程度上取决于良好的设计,调优技巧只是一个辅助手段。
- 调优过程是迭代渐进的过程,每一次调优的结果都要反馈到后续的代码开发中去。
- 性能调优不能以牺牲代码的可读性和可维护性为代价
推荐阅读
- 热闹中的孤独
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 放屁有这三个特征的,请注意啦!这说明你的身体毒素太多
- 一个人的旅行,三亚
- 布丽吉特,人生绝对的赢家
- 慢慢的美丽
- 尽力
- 一个小故事,我的思考。
- 家乡的那条小河
- 《真与假的困惑》???|《真与假的困惑》??? ——致良知是一种伟大的力量