阿里巴巴在开源压测工具 JMeter 上的实践和优化
简介:Apache JMeter 是 Apach 旗下的开源压测工具,创建于 1999 年初,迄今已有超过 20 年历史。JMeter 功能丰富,社区(用户群体)庞大,是主流开源压测工具之一。
作者:灵苒、涧泉
Apache JMeter[1] 是 Apach 旗下的开源压测工具,创建于 1999 年初,迄今已有超过 20 年历史。JMeter 功能丰富,社区(用户群体)庞大,是主流开源压测工具之一。
性能测试通常集中在新系统上线或大型活动前(如电商大促,春节活动等),以验证系统能力,帮助排查定位性能瓶颈等问题。
一次压测活动可粗略分为几个步骤:
- 场景配置。配置压测场景模拟用户(业务)与系统的交互。
- 压测执行。按指定压力量级启动压测。
- 压测监控分析。压测中通常关注施压 RPS,成功率,业务响应时间(RT),网络带宽等关键指标。
- 报告总结。披露系统能力是否符合要求,同时沉淀记录系统性能演变和优化过程。
原生 JMeter 实施压测
在 JMeter 的 GUI 页面编辑压测脚本,点击开始按钮调试 JMeter 脚本,具体操作可参考 JMeter官网[1]。
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 脚本。
施压配置 页面,并发数设置为 50,压测时长设置为 2 分钟。
点击保存去压测,弹出提示框点击确认,PTS 即开始在云端引擎执行 JMeter 脚本发起压力。
通过 OpenAPI 实践 JMeter
云计算会发展成像水电煤一样,成为社会的基础设施。OpenAPI 好比一条条快速管道,连接着企业和阿里云,把资源源源不断的输送给企业。使用云计算来构建 IT 基础设施是未来的发展趋势,这一点已经成为社会共识。OpenAPI 是云服务开放的重要窗口,没有 OpenAPI 的云服务将很难被客户的系统所集成,既影响了用户体验,也制约了云厂商本身的发展。同样的,在压测领域,随着压测需求日益多样化,更多用户希望将云上的压测能力继承到自己的系统,或者根据自己的业务系统,编排自定义的压测平台,从而实现自动化定制化压测需求。
以下代码实现了使用 PTS 的 OpenAPI 一键启动 JMeter 压测场景,并且在完成压测后查看压测报告。
引入 pom 依赖
<!--创建PTS场景需要的实体类,如果只使用JMeter压测则不需要引入--> <dependency> <groupId>com.aliyun</groupId> <artifactId>pts-api-entity</artifactId> <version>1.0.1</version> </dependency> <!--PTS Java SDK依赖。--> <dependency> <groupId>com.aliyun</groupId> <artifactId>pts20201020</artifactId> <version>1.8.10</version> </dependency> <!--阿里云核心库。--> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>4.5.2</version> </dependency>
复制下列代码
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<Map<String, ?>> logs = getJMeterLogsResponse.getBody().getLogs(); // 查看采样器聚合数据 GetJMeterSampleMetricsResponse getJMeterSampleMetrics = getJMeterSampleMetrics(client, reportId); List<String> sampleMetricList = getJMeterSampleMetrics.getBody().getSampleMetricList(); // 查看采样日志 GetJMeterSamplingLogsResponse getJMeterSamplingLogs = getJMeterSamplingLogs(client, reportId); List<String> 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<SaveOpenJMeterSceneRequest.SaveOpenJMeterSceneRequestOpenJMeterSceneFileList> fileList = new ArrayList<SaveOpenJMeterSceneRequest.SaveOpenJMeterSceneRequestOpenJMeterSceneFileList>(); // 设置文件的属性 需要设置文件的名称和文件公网可访问的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 脚本,点击 PTS-JMeter 启动按钮开始压测
压测过程中,JMeter 图形界面会显示部分压测指标,用户可随时去控制台查看压测进程。压测结束后,PTS 会生成更加详细的压测报告,默认保留 30 天,用户可随时去控制台查看。
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,成功率,响应时间,网络带宽等监控数据的变化趋势图。
业务 API 采样日志
很多时候我们还希望看到一个具体请求执行的详细信息。如有 1% 的请求失败,需要查看完整的请求、响应内容,以排查失败原因等。JMeter 图形界面下测试脚本时,可添加 View Results Tree 查看单个请求的详细信息,但执行压力测试时,对每个请求都记录详细信息,不仅没有必要,而且非常耗费资源,影响施压性能。
阿里云 PTS 采取了一个折中的办法,施压引擎间隔一段时间对每个业务 API(压测Sampler)分别采样记录一条成功和失败(如果有)的请求详细信息。在压测中或压测报告页面,点击查看采样日志按钮即可查询记录的请求采样信息,并支持按业务 API(压测Sampler),响应状态(是否成功),请求时间等进行搜索过滤。
JMeter 日志
本地执行 JMeter 脚本时,默认将日志记录到 jmeter.log 文件。在 PTS 上执行 JMeter 脚本时,可通过 JMeter 日志页面实时查看 JMeter 日志,并支持根据日志级别、时间或线程名进行查询过滤。
报告总结
压测结束后,PTS 将汇总监控数据形成压测报告。用户根据压测报告分析评估系统性能是否符合要求,如 RPS,成功率和 RT(响应时间)是否符合期望。并可辅助用户排查分析业务系统性能瓶颈。
PTS 压测报告页面可查询历史压测报告列表。
本文为阿里云原创内容,未经允许不得转载。