阿里巴巴在开源压测工具|阿里巴巴在开源压测工具 JMeter 上的实践和优化

作者:灵苒、涧泉
Apache JMeter [1]是 Apach 旗下的开源压测工具,创建于 1999 年初,迄今已有超过 20 年历史。JMeter 功能丰富,社区(用户群体)庞大,是主流开源压测工具之一。
性能测试通常集中在新系统上线或大型活动前(如电商大促,春节活动等),以验证系统能力,帮助排查定位性能瓶颈等问题。
一次压测活动可粗略分为几个步骤:

  1. 场景配置。配置压测场景模拟用户(业务)与系统的交互。
  2. 压测执行。按指定压力量级启动压测。
  3. 压测监控分析。压测中通常关注施压 RPS,成功率,业务响应时间(RT),网络带宽等关键指标。
  4. 报告总结。披露系统能力是否符合要求,同时沉淀记录系统性能演变和优化过程。
原生 JMeter 实施压测 在 JMeter 的 GUI 页面编辑压测脚本,点击开始按钮调试 JMeter 脚本,具体操作可参考 JMeter官网 [1] 。
阿里巴巴在开源压测工具|阿里巴巴在开源压测工具 JMeter 上的实践和优化
文章图片

对于场景简单,要求测试并发量不高的情况下,JMeter 本地测试就能满足需求。但随着互联网用户的增加,对系统承载更大并发的需求日渐提升,而单台 JMeter 施压机的施压能力有一定上限,所以需要使用多台施压机,以提高 JMeter 的施压能力,这就要使用到 JMeter 的分布式施压功能。
JMeter 的分布式压测需要用户自己管理维护多台机器,使用过程中注意以下几点:
  • 施压机的防火墙已关闭或打开了正确的端口。为 RMI 设置了 SSL 或禁用了它。
  • 所有施压机都在同一个子网上。如果使用 192.xxx 或 10.xxx IP 地址,则服务器位于同一子网中。
  • 所有施压机上使用相同版本的 JMeter 和 Java。
  • 所有施压机都已经拷贝了切分好的 CSV 数据文件、依赖 jar 包等。
  • 压测过程中需要监控施压机是否正常发流量,保持压力与配置一致。
  • 施压前配置好监控数据的收集,方便压测结束后报告的生成。
由此可见 JMeter 的分布式压测需要协调各资源,前置准备以及施压过程维护施压引擎比较麻烦,对实施压测的人员来说压测效率低。
云上的 JMeter 实践 阿里巴巴有着非常丰富的业务形态,每一种业务形态背后都由一系列分布式的技术体系提供服务,随着业务的快速发展,特别是在双 11 等大促营销等活动场景下,准确评估整个业务站点的服务能力成为一大技术难题。
在这个过程中,我们打造了自己的全链路压测系统,以应对更复杂、更多样的压测需求,并将此技术输出到 性能测试 PTS 上,同时支持原生 JMeter 压测。
通过控制台实践 JMeter
上传脚本 打开 PTS 控制台 [2] 主页,左侧导航栏选择压测中心 > 创建场景 > JMeter 压测 ,新建 JMeter 压测场景。填写场景名,如 jmeter-test 。场景配置页面点击上传文件按钮,上传本地测试通过的 test.jmx 脚本。
阿里巴巴在开源压测工具|阿里巴巴在开源压测工具 JMeter 上的实践和优化
文章图片

施压配置 施压配置 页面,并发数设置为 50,压测时长设置为 2 分钟。
阿里巴巴在开源压测工具|阿里巴巴在开源压测工具 JMeter 上的实践和优化
文章图片

保存压测 点击保存去压测,弹出提示框点击确认,PTS 即开始在云端引擎执行 JMeter 脚本发起压力。
阿里巴巴在开源压测工具|阿里巴巴在开源压测工具 JMeter 上的实践和优化
文章图片

压测中页面如下:
阿里巴巴在开源压测工具|阿里巴巴在开源压测工具 JMeter 上的实践和优化
文章图片

注意:因为机器配置和网络环境的差异(PTS 施压机默认为 4 核 8G,BGP 多线路公网),PTS 上压测结果可能与本地压测结果存在一定差异。另外,PTS 上的施压配置会覆盖原脚本中的配置,原脚本无论是写死固定配置还是使用 JMeter 属性配置都没关系。
通过 OpenAPI 实践 JMeter
云计算会发展成像水电煤一样,成为社会的基础设施。OpenAPI 好比一条条快速管道,连接着企业和阿里云,把资源源源不断的输送给企业。使用云计算来构建 IT 基础设施是未来的发展趋势,这一点已经成为社会共识。OpenAPI 是云服务开放的重要窗口,没有 OpenAPI 的云服务将很难被客户的系统所集成,既影响了用户体验,也制约了云厂商本身的发展。同样的,在压测领域,随着压测需求日益多样化,更多用户希望将云上的压测能力继承到自己的系统,或者根据自己的业务系统,编排自定义的压测平台,从而实现自动化定制化压测需求。
以下代码实现了使用 PTS 的 OpenAPI 一键启动 JMeter 压测场景,并且在完成压测后查看压测报告。
引入 pom 依赖
com.aliyun pts-api-entity 1.0.1 com.aliyun pts20201020 1.8.10 com.aliyun aliyun-java-sdk-core 4.5.2

复制下列代码
import com.aliyun.pts20201020.Client; import com.aliyun.pts20201020.models.*; import com.aliyun.teaopenapi.models.Config; import java.util.ArrayList; import java.util.List; import java.util.Map; public class StartingDemo {public static void main(String[] args) throws Exception { Client client = getClient(); // 创建场景 String sceneId = createScene(client); // 启动场景 String reportId = startTesting(client, sceneId); // 最多等待次数 int count = 0; // 查询是否已生成报告 while (!hasReport(client, reportId) && count++ < 20) { // 若报告还未生成,则等待(30s)一段时间再查询 // 根据压测时间酌情等待 Thread.sleep(30 * 1000); } // 查看报告 getJMeterReport(client, reportId); }private static boolean hasReport(Client client, String reportId) throws Exception { ListJMeterReportsRequest request = new ListJMeterReportsRequest(); // 分页设置 request.setPageNumber(1); request.setPageSize(1); // 查询条件设置 request.setReportId(reportId); ListJMeterReportsResponse response = client.listJMeterReports(request); return response.getBody().getReports().size() > 0; }private static void getJMeterReport(Client client, String reportId) throws Exception { // 查看机器日志 GetJMeterLogsResponse getJMeterLogsResponse = getJMeterLogs(client, reportId); List logs = getJMeterLogsResponse.getBody().getLogs(); // 查看采样器聚合数据 GetJMeterSampleMetricsResponse getJMeterSampleMetrics = getJMeterSampleMetrics(client, reportId); List sampleMetricList = getJMeterSampleMetrics.getBody().getSampleMetricList(); // 查看采样日志 GetJMeterSamplingLogsResponse getJMeterSamplingLogs = getJMeterSamplingLogs(client, reportId); List sampleResults = getJMeterSamplingLogs.getBody().getSampleResults(); }private static GetJMeterSamplingLogsResponse getJMeterSamplingLogs(Client client, String reportId) throws Exception { GetJMeterSamplingLogsRequest request = new GetJMeterSamplingLogsRequest(); // 分页设置 request.setPageNumber(1); request.setPageSize(10); // 条件设置 request.setReportId(reportId); GetJMeterSamplingLogsResponse response = client.getJMeterSamplingLogs(request); return response; }private static GetJMeterSampleMetricsResponse getJMeterSampleMetrics(Client client, String reportId) throws Exception { GetJMeterSampleMetricsRequest request = new GetJMeterSampleMetricsRequest(); // 设置报告id request.setReportId(reportId); GetJMeterSampleMetricsResponse response = client.getJMeterSampleMetrics(request); return response; }private static GetJMeterLogsResponse getJMeterLogs(Client client, String reportId) throws Exception { GetJMeterLogsRequest request = new GetJMeterLogsRequest(); // 分页设置 request.setPageNumber(1); request.setPageSize(10); // 查询的压测引擎索引 request.setReportId(reportId); GetJMeterLogsResponse response = client.getJMeterLogs(request); return response; }private static String startTesting(Client client, String sceneId) throws Exception { StartTestingJMeterSceneResponse startTestingSceneResponse = startTestingScene(client, sceneId); String reportId = startTestingSceneResponse.getBody().getReportId(); return reportId; }private static StartTestingJMeterSceneResponse startTestingScene(Client client, String sceneId) throws Exception { StartTestingJMeterSceneRequest request = new StartTestingJMeterSceneRequest(); request.setSceneId(sceneId); StartTestingJMeterSceneResponse response = client.startTestingJMeterScene(request); return response; }private static String createScene(Client client) throws Exception { SaveOpenJMeterSceneRequest request = new SaveOpenJMeterSceneRequest(); // 定义场景 SaveOpenJMeterSceneRequest.SaveOpenJMeterSceneRequestOpenJMeterScene scene = new SaveOpenJMeterSceneRequest.SaveOpenJMeterSceneRequestOpenJMeterScene(); // 设置场景名 scene.setSceneName("test"); // 设置文件列表,包括JMeter脚本、JMeter压测依赖jar包、配置额度数据文件等 List fileList = new ArrayList(); // 设置文件的属性 需要设置文件的名称和文件公网可访问的oss地址 SaveOpenJMeterSceneRequest.SaveOpenJMeterSceneRequestOpenJMeterSceneFileList testFile = new SaveOpenJMeterSceneRequest.SaveOpenJMeterSceneRequestOpenJMeterSceneFileList(); testFile.setFileName("baidu.jmx"); testFile.setFileOssAddress("https://pts-openapi-test.oss-cn-shanghai.aliyuncs.com/baidu.jmx"); fileList.add(testFile); scene.setFileList(fileList); // 设置场景并发,可设置为100万 scene.setConcurrency(1000000); // 设置引擎数量 说明:一台引擎最多能发500并发,最少1并发所以此处能设置的引擎数为[2,1000],另外引擎数量越多消耗vum越快 scene.setAgentCount(2000); // 设置压测持续时间 60s scene.setDuration(60); // 设置测试文件的名称,这个文件需包括在文件列表中 scene.setTestFile("baidu.jmx"); request.setOpenJMeterScene(scene); SaveOpenJMeterSceneResponse response = client.saveOpenJMeterScene(request); return response.getBody().getSceneId(); }private static Client getClient() throws Exception { // 填写自己的AK/SK String accessKeyId = "ak"; String accessKeySecret = "sk"; Config config = new Config(); config.setAccessKeyId(accessKeyId); config.setAccessKeySecret(accessKeySecret); Client client = new Client(config); return client; } }

填写自己的 ak/sk 在上述代码的 getClient 中填写正确的 ak/sk
点击启动 点击 main 方法启动
通过插件实践 JMeter
对于长期使用 JMeter 的用户来说,学习一款新的压测工具还是需要一定的时间成本。因此,PTS开发了一款 PTS-JMeter 插件,可帮助 JMeter 用户在不改变原来的压测行为下直接使用 PTS 的压测资源。用户几乎不感知 PTS-JMeter 插件的存在,与原生 JMeter 使用方式一致,保存/打开 JMeter 脚本点击启动压测即可。
下载安装 点击链接下载最新版本 jar 包 [3]
将 jar 包拷贝到 JMeter 主目录下的 lib/ext 扩展目录下
阿里巴巴在开源压测工具|阿里巴巴在开源压测工具 JMeter 上的实践和优化
文章图片

点击压测 新建 JMeter 脚本,或者打开已有 JMeter 脚本,点击 PTS-JMeter 启动按钮开始压测
阿里巴巴在开源压测工具|阿里巴巴在开源压测工具 JMeter 上的实践和优化
文章图片

查看报告 压测过程中,JMeter 图形界面会显示部分压测指标,用户可随时去控制台查看压测进程。压测结束后,PTS 会生成更加详细的压测报告,默认保留 30 天,用户可随时去控制台查看。
阿里巴巴在开源压测工具|阿里巴巴在开源压测工具 JMeter 上的实践和优化
文章图片

其他 PTS-JMeter 插件更详细的使用方式可以去 PTS 帮助文档 [4] 中查看。
压测监控分析 性能测试不仅仅是简单的发起压力,对压力负载(RPS,网络带宽等)和业务表现(RT,成功率等)的监控和分析也是压测活动的重要组成部分。JMeter 脚本中每个请求节点(Sampler)可设置一个具有业务含义的名字(如 home 和 download page ),我们可称之为业务 API 。JMeter 监控统计按业务 API 名字汇总,如两个名字相同的请求节点将汇总统计为一个业务 API 。配置脚本时需注意,不同业务 API 节点应配置为不同的名字。
业务 API 压力负载和表现
实际工作中,不同业务 API 的统计数据可能存在巨大差异(如浏览商品 RT 通常比提交订单快很多),因此 PTS 默认将各个业务 API 独立统计展示(如上述压测中页面展示的 home 和 download page)。
压测中每个时间点的数据 PTS 都在后台记录了下来,最终将形成完整直观的压测报告。点击业务 API 实时监控趋势图按钮 ,即可查看对应的 RPS,成功率,响应时间,网络带宽等监控数据的变化趋势图。
阿里巴巴在开源压测工具|阿里巴巴在开源压测工具 JMeter 上的实践和优化
文章图片

业务 API 采样日志
很多时候我们还希望看到一个具体请求执行的详细信息。如有 1% 的请求失败,需要查看完整的请求、响应内容,以排查失败原因等。JMeter 图形界面下测试脚本时,可添加 View Results Tree 查看单个请求的详细信息,但执行压力测试时,对每个请求都记录详细信息,不仅没有必要,而且非常耗费资源,影响施压性能。
阿里云 PTS 采取了一个折中的办法,施压引擎间隔一段时间对每个业务 API(压测Sampler)分别采样记录一条成功和失败(如果有)的请求详细信息。在压测中或压测报告页面,点击查看采样日志按钮即可查询记录的请求采样信息,并支持按业务 API(压测Sampler),响应状态(是否成功),请求时间等进行搜索过滤。
阿里巴巴在开源压测工具|阿里巴巴在开源压测工具 JMeter 上的实践和优化
文章图片

点击查看详情即可看到单个请求的详细信息。目前对详细信息提供了通用和 HTTP 两种展示模板,HTTP 展示模板可针对 HTTP 请求进行更友好的排版展示,展示内容包括请求 URL,请求方法,返回码,完整的请求头、请求体,响应头、响应体等。
阿里巴巴在开源压测工具|阿里巴巴在开源压测工具 JMeter 上的实践和优化
文章图片

因为页面上只展示文本内容,请求体或响应体包含图片等无法识别为文本的内容时,可能显示为乱码。另外当请求体或响应体很大时,对应的内容可能被截断。
JMeter 日志
本地执行 JMeter 脚本时,默认将日志记录到 jmeter.log 文件。在 PTS 上执行 JMeter 脚本时,可通过 JMeter 日志页面实时查看 JMeter 日志,并支持根据日志级别、时间或线程名进行查询过滤。
阿里巴巴在开源压测工具|阿里巴巴在开源压测工具 JMeter 上的实践和优化
文章图片

JMeter 日志主要用于脚本执行报错时排查错误原因。一些插件可能通过 JMeter 日志输出一些重要信息,用户在 groovy 脚本等代码中也可以直接打印日志。
报告总结 压测结束后,PTS 将汇总监控数据形成压测报告。用户根据压测报告分析评估系统性能是否符合要求,如 RPS,成功率和 RT(响应时间)是否符合期望。并可辅助用户排查分析业务系统性能瓶颈。
PTS 压测报告页面可查询历史压测报告列表。
阿里巴巴在开源压测工具|阿里巴巴在开源压测工具 JMeter 上的实践和优化
文章图片

点击查看报告打开查看报告详情。压测报告在 PTS 上默认保存 30 天,可点击报告导出按钮,导出保存 PDF 版压测报告到本地。压测报告概要信息包括压测执行时间,RPS,RT,成功率等概要数据。场景详情包含全场景维度和业务 API 维度的监控统计信息。
阿里巴巴在开源压测工具|阿里巴巴在开源压测工具 JMeter 上的实践和优化
文章图片

相比手动命令行执行 JMeter 脚本,PTS 更加简单易用,提供简单直观的监控,并提供海量施压能力 。
相关链接? [1] Apache JMeter 官网:
[](https://jmeter.apache.org/)
https://jmeter.apache.org/
[2] PTS 控制台:
[](https://pts.console.aliyun.co...)
https://pts.console.aliyun.co...
[3] 点击链接下载最新版本 jar 包:
[](https://pts.console.aliyun.co...)
https://pts.console.aliyun.co...
[](https://jmeter-pts-testing-ve...)
[](https://jmeter-pts-testing-ve...)https://jmeter-pts-testing-ve...
[4]PTS 帮助文档:??
[](https://help.aliyun.com/docum...)
??https://help.aliyun.com/document_detail/379921.html??
【阿里巴巴在开源压测工具|阿里巴巴在开源压测工具 JMeter 上的实践和优化】??

    推荐阅读