项目背景

随着业务拓展,对于接口性能的要求也在上升,各部门也开始针对部分慢接口进行优化,从测试角度针对这些优化需求进行测试时不仅要保证对应接口的功能正常使用同时也要验证接口优化成果。在日常的开发工作中一些后台服务配置的改动也会对接口的性能产生影响,我们急需要一套性能压测巡检平台,对平时接口服务的技改,后端配置的优化进行持续的性能验证。

项目日常痛点

1.测试与开发都无法确定这次改动优化程度如何,与之前比较究竟提升多少性能。只能验证相关功能正常。

2.测试不清楚开发优化接口用了那些方法需要观察那些指标。

3.优化接口的改动与新增接口是否存在性能bug无法确定。

为了清楚的展示每次优化的成果,对新上线的接口检查是否存在性能bug。基于目前的zat自动化平台开发了性能压测功能。

ZAT性能压测巡检实现方法

1.ZAT性能巡检平台实现机制介绍

①平台后台使用jmeter执行自动化case本身就支持对于后端接口的性能压测,通过对平台的改造可以快速实现在平台上对于接口性能压测的功能。而不需要重新开发一套工具。

②性能压测使用的case可以直接复制自动化case,修改几个变量的配置可以直接用于性能压测。节省编写压测用例的时间,同时使用自动化用例还可以模拟用户使用场景。

③压测结果通过平台读取直接存入数据库,可以对多次性能压测数据比对,接口优化时也可以通过历史数据来比对是否有优化。

2.后期还需要解决的问题

Jmeter运行压测时占用系统资源多,同时目前针对uat环境进行压测大部分服务只有1台机器,为了放置多部门压测时对公共服务的调用影响到接口性能的数据。所以现在设置成同一时间只允许运行1个压测job。其他端可以看到当前那个组在压测状态和进度。

之后会采取2种修改改进这个问题:

①采取分布式压测,各组可以自己提供压测机器通过slave的方式分担压测压力。②等服务端接入docker后,通过各种策略将请求发送到对应容器。避免多个部门压测同时调用同一个公共服务导致影响到性能数据ZAT性能压测巡检平台实现架构图

ZAT性能压测巡检平台流程图

目前zat自动化平台后端使用jmeter来执行case。基于原有功能在之前的基础上加入jmeter自身线程组与压测所需的csv数据文件的设置进行改造,实现了压测用例的生成,执行。通过对jtl文件中各性能数据的计算获取接口相应时间,吞吐量,95线,99线等数据,同时调用grafana接口获取压测时对应服务器的内存,cpu和负载数据。汇总收集到的数据存入数据库。用来跟历史数据或者之后的数据进行比对。

ZAT性能压测巡检平台的实现ZAT性能压测巡检平台执行压测的流程如下

API信息设置

压测中需要关注对应的服务器的内存 CPU所以要在对应压测case所使用的API信息中增加对应的APPID与需要压测的IP地址

压测Case的生成落地

1.线程组设置

为了测试接口性能,需要模拟大量用户发送请求的场景所以在自动化case的基础上增加了对线程组数据的设置,通过这个设置可以模拟大量用户访问。

a.普通线程组(普通压测设置)线程数代表起多少个线程发送请求;Ramp-up代表多少时间内发送完成;循环次数代表每个进程循环发送多少次;对应jmx模板实现代码如下:

 1{% if test_group.type ==  'normal' %} 2    <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="{{ test_group.group_name }}" 3                 enabled="true"> 4        <stringProp name="ThreadGroup.on_sample_error">continuestringProp> 5        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" 6                     testclass="LoopController" testname="Loop Controller" enabled="true"> 7            <boolProp name="LoopController.continue_forever">falseboolProp> 8            <stringProp name="LoopController.loops">{{ test_group.loopTime }}stringProp> 9        elementProp>10        <stringProp name="ThreadGroup.num_threads">{{ test_group.thread }}stringProp>11        <stringProp name="ThreadGroup.ramp_time">{{ test_group.LastTime }}stringProp>12        <boolProp name="ThreadGroup.scheduler">falseboolProp>13        <stringProp name="ThreadGroup.duration">stringProp>14        <stringProp name="ThreadGroup.delay">stringProp>15    ThreadGroup>16{% endif %}

b.梯度线程组(模拟梯度加压)线程数代表总共起多少线程;持续时间代表启动的线程总数达到最大值之后,再持续运行60秒;初始进程代表设置最开始时启动多少个线程;上升梯度与时间代表每隔多少时间启动多少进程,下降梯度与时间同理对应jmx模板实现如下:

 1{% if test_group.type ==  'step' %} 2    <kg.apc.jmeter.threads.SteppingThreadGroup guiclass="kg.apc.jmeter.threads.SteppingThreadGroupGui" 3                                               testclass="kg.apc.jmeter.threads.SteppingThreadGroup" 4                                               testname="{{ test_group.group_name }}" enabled="true"> 5        <stringProp name="ThreadGroup.on_sample_error">continuestringProp> 6        <stringProp name="ThreadGroup.num_threads">{{ test_group.thread }}stringProp> 7        <stringProp name="Threads initial delay">0stringProp> 8        <stringProp name="Start users count">{{ test_group.start_thread }}stringProp> 9        <stringProp name="Start users count burst">{{ test_group.increase_thread }}stringProp>10        <stringProp name="Start users period">{{ test_group.period_time }}stringProp>11        <stringProp name="Stop users count">{{ test_group.decrease_thread }}stringProp>12        <stringProp name="Stop users period">{{ test_group.decrease_period_time }}stringProp>13        <stringProp name="flighttime">{{ test_group.last_time }}stringProp>14        <stringProp name="rampUp">stringProp>15        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel"16                     testclass="LoopController" testname="Loop Controller" enabled="true">17            <boolProp name="LoopController.continue_forever">falseboolProp>18            <intProp name="LoopController.loops">-1intProp>19        elementProp>20    kg.apc.jmeter.threads.SteppingThreadGroup>21{% endif %}

2.Csv文件设置(非必填)

考虑到许多接口的数据会存入redis,有时候使用固定的数据对接口进行压测时接口直接从redis读取数据无法模型正常用户流程,所以通过绑定csv文件去切换变量(账号,id等)模拟不同用户发送请求

Csv数据设置

CSV文件会上传到服务器抱错 并且生成压测用例时会绑定到对应计划中变量名对应jmeter从csv文件中取值时对应列的数据对应的参数名称用逗号分割

Csv存储代码实现

 1try: 2    CsvFile = request.FILES.get("CsvFile") 3    Paraments = request.POST.get("paraments") 4    FilePath = rootPath + "/StressCsvStore/{}-{}-{}.csv".format(project.name, alias, case_id) 5    default_storage.save(FilePath, ContentFile(CsvFile.read())) 6    CsvDb = StressCsvPath() 7    CsvDb.CsvPath = FilePath 8    CsvDb.Parement = Paraments 9    CsvDb.save()10    CsvID = CsvDb.id11except Exception as e:12    CsvID = 0

Csv插件模板实现

 1{% if test_group.CsvPath !=  '' %} 2    <CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="CSV Data Set Config" enabled="true"> 3      <stringProp name="delimiter">,stringProp> 4      <stringProp name="fileEncoding">stringProp> 5      <stringProp name="filename">{{test_group.CsvPath}}stringProp> 6      <boolProp name="ignoreFirstLine">falseboolProp> 7      <boolProp name="quotedData">falseboolProp> 8      <boolProp name="recycle">trueboolProp> 9      <stringProp name="shareMode">shareMode.allstringProp>10      <boolProp name="stopThread">falseboolProp>11      <stringProp name="variableNames">{{test_group.CsvParaments}}stringProp>12    CSVDataSet>13    <hashTree/>14{% endif %}

3.Case选择用例列表自动筛选压测用例目录下的用例选择对应用例创建压测计划

压测执行

1.压测执行代码实现

1try:2    RunningProcess = subprocess.Popen(r'{} -n -t "{}" -j "{}"'.format(jmeter_abspath, tmp_jmx_abspath, tmp_log_abspath))3    RunningId = str(data["project_id"]) + "-" + str(RunningProcess.pid)4    RunningProcess.wait()5    server_running.remove(ShowContent)6except Exception as Error:7    server_running.remove(ShowContent)8    os.remove(tmp_jmx_abspath)9    return JsonResponse(code="999998", msg="发生{}错误".format(Error))    

2.压测执行前端展示

压测实时监控

压测完成后从对应服务器的grafana接口获取服务器信息实现实时监控

 1if hostip: 2    mem_query = 'query=(1 - (node_memory_MemAvailable_bytes{instance=~"' + hostip + ':9100"} / (node_memory_MemTotal_bytes{instance=~"' + hostip + ':9100"}))) * 100' 3    max_mem, average_mem = getInfoFromGranafa(startTime, endTime, mem_query) 4    cpu_query = 'query=avg(irate(node_cpu_seconds_total{instance=~"' + hostip + ':9100",mode="user"}[2m]))*100' 5    max_cpu, average_cpu = getInfoFromGranafa(startTime, endTime, cpu_query) 6    load_query = 'query=node_load1{instance=~"' + hostip + ':9100"}' 7    max_load, average_load = getInfoFromGranafa(startTime, endTime, load_query) 8    tr.mem_max = max_mem 9    tr.men_average = average_mem10    tr.cpu_max = max_cpu11    tr.cpu_average = average_cpu12    tr.load_max = max_load13    tr.load_average = average_load14tr.save()    

压测时实时数据展示

压测时服务器性能数据实时展示

压测报告比对结果的实现

压测报告比对代码

 1fillColor: function(obj) { 2if (obj.columnIndex===2){ 3    if (obj.row.cpu_max.indexOf('/') >= 0) { 4        return; 5    } 6    let strs = obj.row.cpu_max.split(" "); 7    if (strs.length 3) { 8        return; 9    }10    let ratio = Math.abs((parseFloat(strs[0]) - parseFloat(strs[2]))/parseFloat(strs[0]));11    if (ratio 0.2) {12            return;13    }14    if (obj.row.cpu_max.indexOf('↑')!==-1){15        return {color:'red'};16    } else {17        return {color:'blue'};18    }19}   

后端比对数据实现

 1if int(left_data.get('FiftyLine')) int(right_data.get('FiftyLine')): 2    merge_data['FiftyLine'] = left_data.get('FiftyLine') + '  ' + right_data.get('FiftyLine') + ' ↑' 3else: 4    merge_data['FiftyLine'] = left_data.get('FiftyLine') + '  ' + right_data.get('FiftyLine') 5 6if int(left_data.get('NintyLine')) int(right_data.get('NintyLine')): 7    merge_data['NintyLine'] = left_data.get('NintyLine') + '  ' + right_data.get('NintyLine') + ' ↑' 8else: 9    merge_data['NintyLine'] = left_data.get('NintyLine') + '  ' + right_data.get('NintyLine')1011if int(left_data.get('NintyNineLine')) int(right_data.get('NintyNineLine')):12    merge_data['NintyNineLine'] = left_data.get('NintyNineLine') + '  ' + right_data.get(13        'NintyNineLine') + ' ↑'14else:15    merge_data['NintyNineLine'] = left_data.get('NintyNineLine') + '  ' + right_data.get(16        'NintyNineLine')

前端判断数据是否超过20%阈值修改颜色展示

通过结果的比对,我们可以非常清晰看到本轮技改或者优化配置,性能提升多少还是下降,也能方便的查看到各种资源池的比对。

另外,以每次压测的结果作为下一次比对性能基线,从而推动性能优化工作持续进行。

本文作者

韩盛,7年测试经验,擅长性能测试,自动化测试。熟悉java, python。现任职掌门1对1测试开发工程师。

刘万红,多年互联网大厂测试经历,现任职掌门1对1研发部测试经理 擅长接口/性能/自动化等各种测试平台开发。

压测接口线程数设置_ZAT掌门性能压测巡检系统实战和落地相关推荐

  1. 多线程线程数设置多少合适

    前沿 大家都用过线程池,但是线程池数量设置为多少比较合理呢? 线程数的设置的最主要的目的是为了充分并合理地使用 CPU 和内存等资源,从而最大限度地提高程序的性能,因此让我们一起去探索吧! 首先要考虑 ...

  2. 线程池——核心线程数设置依据

    根据线程数设置依据 最大线程数:原则上就是性能最高线程数,因为此时性能已经是最高,再设置比他大的线程数反而性能变低.极端情况下才会使用到最大线程数,正常情况下不应频繁出现超过核心线程数的创建. 核心线 ...

  3. 线程池核心数与最大线程数设置

    线程池核心数与最大线程数设置 总结: 核心线程 CPU密集型:核心线程数=CPU核心数(或 核心线程数=CPU核心数+1) I/O密集型:核心线程数=2*CPU核心数(或 核心线程数=CPU核心数/( ...

  4. java压测请求线程数_程序员撕开京东 618 大促压测的另一面 | 原力计划

    作者 | 天涯泪小武 责编 | 王晓曼 出品 | CSDN博客 前天618大促演练进行了全链路压测,在此之前刚好我的热key探测框架也已经上线灰度一周了,小范围上线了几千台服务器,每秒大概接收几千个k ...

  5. 【Pytorch】物理cpu、逻辑cpu、cpu核数、pytorch线程数设置

    上周末写ddp,常常遇到中途退出的问题,解决中途遇到了很多CPU线程数和核心数的问题,记录如下 1. 物理cpu.逻辑cpu.cpu核数.超线程 这一部分主要来自什么是物理cpu,什么是逻辑cpu,什 ...

  6. java 多线程 cpu核数_java线程数设置和系统cpu的关系

    这里的cpu个数不是指系统的cpu总个数,也不是指cpu总核心数,而是指cpu的总逻辑处理单元即超线程的个数. IO密集型程序(如数据库数据交互.文件上传下载.网络数据传输等等)设置线程数为2倍的总逻 ...

  7. 线程池详解和线程池核心数与最大线程数设置

    ThreadPool 为什么要用线程池 1.创建/销毁线程需要消耗系统资源,线程池可以复用已创建的线程. 2.控制并发的数量.并发数量过多,可能会导致资源消耗过多,从而造成服务器崩溃.(主要原因)可以 ...

  8. weblogic9修改线程数设置

    base_domain ---Environment -----Work Managers --------New ------------------ Create a New Work Manag ...

  9. 后端服务性能压测实践

    转自:https://mp.weixin.qq.com/s/XW9geHZ9odHdI7srDiKBIg 目录 背景 环境检测 压力机及压力工具检测 Linux openfiles limit 设置 ...

最新文章

  1. SAP HUM 如何看哪些HU还在923包装区尚未上架?
  2. SQL 存储过程 解析XML
  3. Py之GUI之PyQt:PyQt5的简介、入门、安装(QtCreator和QtDesigner)图文教程之详细攻略
  4. kgdb调试内核无法执行断点及kdb-22:Permisson denied
  5. jquery异步调用post的一些注意事项
  6. [苹果技巧]苹果系统用于系统监控和管理的命令
  7. byte二维数组表示
  8. repo 的几个使用理解
  9. PHP开源的项目管理软件
  10. linux 查看日志
  11. JMeter 安装教程(详细安装教程)
  12. SQL Server 2019 安装教程
  13. jzoj1212 重建道路
  14. 8、content-scripts实现一个简单的去除广告插件
  15. python 提取函数名/变量名(或将函数名/变量名转换为字符串)
  16. HMI-64-【多媒体】Ui全部制作完成
  17. MATLAB 设置纵轴显示范围、科学记数法
  18. sql -803 DB2修改主键起始值
  19. 为什么苹果日历不能设置日程_怎么查看苹果手机日历所有日程?
  20. C语言中的分支结构和循环结构有哪些,【单选题】下面哪种不是C语言中的基本结构______。 A. 顺序结构 B. 分支结构 C. 跳转结构 D. 循环结构...

热门文章

  1. mouseenter 延迟_桃园台服加速器 电狐加速器带你低延迟玩游戏
  2. ant 获取当前url的参数
  3. Vue父组件网络请求回数据后再给子组件传值demo示例
  4. 记一次数据库崩溃的恢复
  5. 数据变金矿:一文读懂序列模型(附用例)
  6. 高可用架构篇 MySQL源码编译安装(CentOS-6.6+MySQL-5.6)
  7. SQL脚本--有关压缩数据库日志
  8. mysql+mycat搭建稳定高可用集群,负载均衡,主备复制,读写分离
  9. Android UI SurfaceView的使用-绘制组合图型,并使其移动
  10. hibernate 全面学习【lazy策略 】