用户访问量不断增长的同时,也伴随着后台站点性能要求的不断提升。很多情况下流量成倍增长所需的机器资源并不是简单的线性增加,更可能会成几何倍数飙涨,这不仅需要我们做好站点的过载保护和容灾,更需要通过压测来查找系统性能瓶颈点和吞吐量上限,提前扩容,避免流量高峰出现大量请求丢弃。那么对线上站点进行模拟压测,将是我们预估站点请求的最大承载量,估算业务站点所需机器资源等的有效手段的手段。
下面将主要介绍nGrinder这款强大的压测工具,一起了解下它的特点、搭建步骤和性能测试功能,帮助我们更好的模拟线上流量压测。
当然,提到压测工具,很多人会想到jmeter,它也是我们经常使用的站点压测工具,那么nGrinder与Jemter之间的对比有什么不同呢?
从上图可以看出,nGriner在性能监控、稳定性和扩展性上比jmeter更具有优势,当然这依赖于开发人员通过对压测脚本等进行二次开发的方式,提升整个压测平台的灵活稳定性。下面将详细介绍nGrinder环境搭建和性能测试功能。
3、agent安装Controller安装完成后,登陆管理后台站点,点击顶部右侧下拉菜单中的Download Agent进行下载。
4、安装monitor
下载monitor,将monitor安装包解压在被压测机器。执行目录下的启动命令:sh run_monitor.sh即可
至此压测环境基本搭建完成,通过管理后台即可查看看部署的agent和monitor节点是否和controller端连接成功。
需要注意的是:请勿将agent和被压测站点部署在同一台机器上(会严重影响被压测站点性能,争抢cpu、内存等机器资源)。monitor节点需要和被压测站点部署在同一台机器上。
三、创建压测脚本
1、简易创建压测脚本
压测站点前,首先需要编写压测脚本。通过点击管理后台上方的script按钮,进入脚本管理页面,即可创建。(在首页quick start输入框中输入目标url也可直接创建脚本,效果相同)
在创建一个脚本时,首先需选择脚本语言(支持Groovy & Jython),填写脚本名,再选择get或post方法访问测试url,下面可添加url请求所需要的额外参数(header、cookie、params)
PS:官方考虑到Groovy和Jython两种语言执行性能上的差别,建议优先使用性能更好的Groovy来编写压测脚本(通过对两种语言for循环10W以上执行时间比较,耗时相差在20倍左右),下图是生成的Groovy脚本页面:
由于Groovy和java之间具有极高的兼容性,很多jdk类库可直接调用,很容易上手编写,对应java开发人员来说,学习成本很小。
2、使用脚本模拟真实稳定压测环境
通常线上web或者服务站点出于性能优化考量,针对各业务场景都大量使用到缓存,导致压测数据相同时,每次命中缓存后快速响应请求,造成站点压测性能极佳的假象(但线上真实请求参数各异)。所有站点做压测时,更希望让每个压测请求都携带不同的线上请求参数,为了满足模拟真实压测环境要求,就需要我们在脚本开发时做如下准备1> 准备线上真实环境下各种参数形式的压测数据2> 让每个压测线程,每次访问目标服务器时,分别携带不同的请求参数3> 控制单个压测线程的单次执行时间,保证各线程能以稳定速率向目标服务器发送请求4>脚本是否能通过传参灵活控制内部逻辑上述几个要求,是我们接下来重点考虑的问题2.1 压测数据准备:我们可以利用tomcat开启的access日志来收集线上真实请求数据(nginx的线上访问日志同样满足),将打印在access日志中的正常格式请求过滤出来,整理成压测数据文件。如果压测的是单台机器,还需要将数据中的域名替换为ip+端口形式,留待压测时使用。下图所示是某线上站点的请求数据:
2.2 各压测线程读取不同数据:由于nGrinder是分布式压测工具,在向每个agent节点下发的测试数据文件都是相同的,这就需要我们在脚本中指定不同的数据文件片段供各线程分别读取。nGrinder为了方便我们让每个线程执行不同的业务逻辑,内置了ScriptContext上下文容器对象,供我们获取当前脚本运行环境下的常用参数,如下图所示:
由于脚本容器在启动时已将所有的agent、进程和线程进行了编号,从0到该类型节点在配置项中的最大值-1。例如当我们使用1个agent节点下配置10个process,每个process下配置100个线程来运行脚本,此时当前线程内获取到的grinder.processNumber值是0到9中的某一个,grinder.threadNumber线程值是0到99中的某一个,这样我们便可以先将整个数据文件按总agent、总进程和总线程数做数据切割,再根据当前线程所对应的进程、线程编号,读取对应的压测数据。如下是在进程初始化时,单个agent下对多进程、多线程压测数据文件切割示例:可先通过如下代码筛选出当前进程可读取的数据片段范围,后续线程执行时,再根据当前线程编号获取各自线程的数据片段,按顺序读取即可:
2.3脚本控制稳定的TPS:由于ngrinder在执行压测任务时,所有启动线程都在循环重复执行脚本,每次请求发送响应时间都是不同的,这种情况下单位时间内单个线程的执行次数是不可控的,TPS会随平均响应时间变慢而下降,无法维持稳定的TPS压测环境。针对这种情况,我们可以在压测脚本中增加Thread.sleep方法,控制单次脚本执行耗时,当并发执行该脚本时,使执行脚本的线程以稳定的时间间隔执行,产生稳定的TPS。
上图所示等待时间,即为1秒减去当前请求耗时,当压测目标站点平均响应时间很长时(如平均超过1秒占比较大),可自行调整等待总时间到2000ms甚至更高的毫秒数,通过脚本逻辑保证单个线程产生的TPS是稳定的2.4 脚本传参方式:通过前面的介绍,了解到控制压测请求TPS需要获取整个环境下的总agent数、进程数和线程数,通过写死当前压测任务下的这些数值会使得每次调整压测Tps时都要去修改脚本,而nGrinder提供了脚本输入参数配置项,我们可以自定义输入配置来传递当前压测任务的总进程、线程数,脚本中使用System.getProperty("param")方法即可获取到输入参数
四、执行压测任务1、选择主页上面的Performance Test选项,进入性能测试列表页中,点击create Test创建压测任务,如下图所示:
2、在配置页中填写agent数量、虚拟用户数、压测脚本、压测时长等配置项,如下图所示
在配置压测环境时,需要遵循如下规定1> 配置项中的vuser虚拟用户数量直接对应脚本并发执行次数(如脚本单次执行时间控制在1秒,且单次执行只发送一次压测请求,则对应被压测站点的TPS),也可通过设置进程、线程数量让平台自动计算所需初始化的进程&线程数,对应关系为vuser数= procsses数 * Thread数。(设置vuser数 或processes + Thread数 两种配置并发效果相同)2> 单个procsses配置的线程数请勿超过200,单个agent支持的总vuser数建议不要超过5000,且保证单个agent运行至少有双核、6G空闲内存可使用(单agent并发量越高,所需内存越大)3> 当被压集群qbs大于3000时,建议启动多个agent来分摊压测进程,来保证压测性能稳定性4> 使用配置项paramter可向压测脚本中传入自定义参数,(如传入进程&线程数,来切分压测数据,让每个线程读取不同的压测数据,达到模拟线上真实请求的目的)
3、配置好压测任务后即可立刻执行压测任务(平台也提供了延迟执行压测任务功能)压测执行结果如下图所示:
我们可以看到,通过脚本内部控制单次请求执行&等待时间,使得整个压测任务在启动后TPS都较为平稳,我们还可以通过查看压测日志文件&详细压测报告(含CSV表格,如下图所示),观察整个压测过程中站点响应时间变化、机器性能状况(需要被压测机安装monitor)等
五、扩展功能介绍
1、支持依赖第三方包 & 压测数据文件获取
nGrinder支持上传压测数据文件 & 第三方类库jar包,灵活支持自定义脚本的扩展性,只需在lib和resource目录中上传依赖的数据文件和jar包,即可在脚本用使用相对路径引用,上传位置如下图所示:
2、自定义数据视图:
下图是自定义方式收集到的压测脚本执行逻辑平均耗时汇总图:
通过上面介绍,我们能看到nGrinder是一款性能稳定可扩展灵活的分布式压测工具,当然,这依赖于开发人员在压测脚本等所做的二次开发编码,我们可使用更贴近java语法的Groovy来降低我们的语言学习成本,更快速的编写出灵活通用的压测脚本,满足我们对站点吞吐量的准确评估需求,更好的规划线上站点的机器数配比,平稳度过全年流量高峰期。