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

简介:Apache JMeter 是 Apach 旗下的开源压测工具,创建于 1999 年初,迄今已有超过 20 年历史。JMeter 功能丰富,社区(用户群体)庞大,是主流开源压测工具之一。

作者:灵苒、涧泉

Apache JMeter[1] 是 Apach 旗下的开源压测工具,创建于 1999 年初,迄今已有超过 20 年历史。JMeter 功能丰富,社区(用户群体)庞大,是主流开源压测工具之一。

性能测试通常集中在新系统上线或大型活动前(如电商大促,春节活动等),以验证系统能力,帮助排查定位性能瓶颈等问题。

一次压测活动可粗略分为几个步骤:

  • 场景配置。配置压测场景模拟用户(业务)与系统的交互。
  • 压测执行。按指定压力量级启动压测。
  • 压测监控分析。压测中通常关注施压 RPS,成功率,业务响应时间(RT),网络带宽等关键指标。
  • 报告总结。披露系统能力是否符合要求,同时沉淀记录系统性能演变和优化过程。

原生 JMeter 实施压测

在 JMeter 的 GUI 页面编辑压测脚本,点击开始按钮调试 JMeter 脚本,具体操作可参考 JMeter官网[1]。

阿里巴巴在开源压测工具 JMeter 上的实践和优化_第1张图片

对于场景简单,要求测试并发量不高的情况下,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 上的实践和优化_第2张图片

施压配置

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

保存压测

点击保存去压测,弹出提示框点击确认,PTS 即开始在云端引擎执行 JMeter 脚本发起压力。

阿里巴巴在开源压测工具 JMeter 上的实践和优化_第4张图片

压测中页面如下:

阿里巴巴在开源压测工具 JMeter 上的实践和优化_第5张图片

注意:因为机器配置和网络环境的差异(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 扩展目录下image.gif

image.png

点击压测

新建 JMeter 脚本,或者打开已有 JMeter 脚本,点击 PTS-JMeter 启动按钮开始压测

阿里巴巴在开源压测工具 JMeter 上的实践和优化_第6张图片

查看报告

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

其他

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,成功率,响应时间,网络带宽等监控数据的变化趋势图。image.gif

阿里巴巴在开源压测工具 JMeter 上的实践和优化_第8张图片

业务 API 采样日志

很多时候我们还希望看到一个具体请求执行的详细信息。如有 1% 的请求失败,需要查看完整的请求、响应内容,以排查失败原因等。JMeter 图形界面下测试脚本时,可添加 View Results Tree 查看单个请求的详细信息,但执行压力测试时,对每个请求都记录详细信息,不仅没有必要,而且非常耗费资源,影响施压性能。

阿里云 PTS 采取了一个折中的办法,施压引擎间隔一段时间对每个业务 API(压测Sampler)分别采样记录一条成功和失败(如果有)的请求详细信息。在压测中或压测报告页面,点击查看采样日志按钮即可查询记录的请求采样信息,并支持按业务 API(压测Sampler),响应状态(是否成功),请求时间等进行搜索过滤。

阿里巴巴在开源压测工具 JMeter 上的实践和优化_第9张图片

点击查看详情即可看到单个请求的详细信息。目前对详细信息提供了通用和 HTTP 两种展示模板,HTTP 展示模板可针对 HTTP 请求进行更友好的排版展示,展示内容包括请求 URL,请求方法,返回码,完整的请求头、请求体,响应头、响应体等。

阿里巴巴在开源压测工具 JMeter 上的实践和优化_第10张图片

因为页面上只展示文本内容,请求体或响应体包含图片等无法识别为文本的内容时,可能显示为乱码。另外当请求体或响应体很大时,对应的内容可能被截断。

JMeter 日志

本地执行 JMeter 脚本时,默认将日志记录到 jmeter.log 文件。在 PTS 上执行 JMeter 脚本时,可通过 JMeter 日志页面实时查看 JMeter 日志,并支持根据日志级别、时间或线程名进行查询过滤。

阿里巴巴在开源压测工具 JMeter 上的实践和优化_第11张图片

image.gifJMeter 日志主要用于脚本执行报错时排查错误原因。一些插件可能通过 JMeter 日志输出一些重要信息,用户在 groovy 脚本等代码中也可以直接打印日志。

报告总结

压测结束后,PTS 将汇总监控数据形成压测报告。用户根据压测报告分析评估系统性能是否符合要求,如 RPS,成功率和 RT(响应时间)是否符合期望。并可辅助用户排查分析业务系统性能瓶颈。

PTS 压测报告页面可查询历史压测报告列表。

阿里巴巴在开源压测工具 JMeter 上的实践和优化_第12张图片

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

阿里巴巴在开源压测工具 JMeter 上的实践和优化_第13张图片

相比手动命令行执行 JMeter 脚本,PTS 更加简单易用,提供简单直观的监控,并提供海量施压能力 。

原文链接
本文为阿里云原创内容,未经允许不得转载。

你可能感兴趣的:(java单元测试)