Jmeter 动态参数压力测试时间段预定接口
🎯 本文档详细介绍了如何使用Apache JMeter进行压力测试,以评估预定接口在高并发场景下的性能表现。通过创建线程组模拟不同数量的用户并发请求,利用CSV文件动态配置时间段ID和用户token,确保了测试数据的真实性和有效性。文档中还展示了如何设置JMeter的各项参数、添加HTTP请求头、查看结果树和聚合报告等操作步骤。最终,通过一次针对4000用户并发的压测实例,分析了样本数、响应时间、异常率及吞吐量等关键指标,验证了系统的稳定性和可靠性。
🏠️ HelloDam/场快订(场馆预定 SaaS 平台)
文章目录
- 压力测试
- Jmeter介绍
- 测试目标
- 数据准备
- Jmeter如何进行操作
- Jmeter设置
- 创建线程组
- 创建 HTTP 请求
- 添加请求头
- 添加查看结果树、聚合报告
- 动态参数
- 给请求参数配置不同的时间段ID
- 依赖
- CSV生成代码
- 绑定CSV文件
- 配置不同的用户token
- CSV 生成
- 绑定 CSV 文件
- 简单测试
- 正式压测
- 测试环境
- 内存预热
- 压力测试
- Jmeter结构
压力测试
为了评估和测量接口在高负载情况下的性能表现。压力测试通常用于确定系统在预期的最大负载下的运行情况,识别系统可能存在的性能瓶颈,以及验证系统的稳定性和可靠性。压力测试对于确保应用程序能够支持特定数量的并发用户或操作至关重要。
Jmeter介绍
Apache JMeter 是一款开源的、基于Java的性能测试工具,主要用于测试静态和动态资源(如静态文件、Java Servlets、CGI脚本、数据库和其他基于Web的应用程序资源等)的性能。它最初设计用于Web应用测试但后来扩展到其他测试领域。JMeter可以用来模拟大量用户并发访问目标服务,以此来分析在不同负载条件下应用的性能表现。此外,它支持多种协议和技术,包括HTTP、HTTPS、FTP、SOAP、REST、LDAP、TCP、SMTP等,极大地增强了其灵活性和适用范围。JMeter的一大优点是它能够以图形界面或命令行模式运行,而且由于它是用Java编写的,因此可以在任何安装了Java虚拟机的平台上使用,具有很好的跨平台性。
- 官网:https://jmeter.apache.org/
- 下载地址:https://jmeter.apache.org/download_jmeter.cgi
测试目标
测试时间段预定接口在不同并发用户下的吞吐量。
数据准备
为了让测试结果更加有参考性,需要尽量模拟现实生活中的预定逻辑,即肯定是有多个用户同时进行预定,且预定的场馆、分区、时间段都可能不同。因此我们需要先模拟生成一些数据,其中包括场馆、分区、时间段模板、时间段。最终需要传给Jmeter的数据有:
- 可接受预定的不同时间段 id
- 不同用户登录之后的 token
Jmeter如何进行操作
Jmeter设置
修改为白色外观

设置为简体中文,方便操作,如果你英语好,当我没说,哈哈哈

创建线程组
创建线程组是一个基础且关键的步骤。线程组主要用来模拟用户对服务器或应用程序发起请求的行为。具体来说,它定义了虚拟用户的数目(即线程数)、这些用户将如何行动以及它们执行动作的时间安排(如启动时间、持续时间和关闭时间)。

初步设置如下参数,后续在进行压力测试的时候,可以从小到大调整线程数等参数
- 一个线程代表一个用户,每个用户对不同时间段发起多次请求,可以先从50个用户开始,逐步增加到500或1000个用户,观察接口的性能变化
- 循环次数:每个线程发请求的数量,相当于一个用户发起多少次预定
- Ramp-Up时间:设置为5秒,表示这1000个线程,会在5秒内均匀启动起来,每个线程之间的启动间隔大约为 5/1000 秒

创建 HTTP 请求
预订接口如下:
@GetMapping("/v1/reserve")
public Result reserve(@RequestParam("timePeriodId") Long timePeriodId) {OrderDO orderDO = timePeriodService.reserve(timePeriodId);return Results.success(orderDO);
}

这里需要设置服务所在IP、端口,以及请求的接口路径。因为预定的时候,需要指明是哪个时间段,所以需要在参数中进行设置

添加请求头
由于用户在预定时间段的时候,需要先从用户登录之后的 token 信息中获知用户是谁,所以我们需要将 token 设置到请求头中

注意:除了设置 token 之外,还需要添加Content-type为application/json,后端接口才能正常解析 json 数据

添加查看结果树、聚合报告
- 结果树:用来查看请求的请求参数、响应结果
- 聚合报告:用来查看这些请求的统计信息

动态参数
因为我们需要模拟不同用户预定不同时间段的行为,这期间用户、时间段都有多个,因此,我们不能写死 HTTP 请求中的请求参数,而是需要使用动态参数,从 CSV 文件中读取数据,然后动态设置到不同的请求中
给请求参数配置不同的时间段ID
依赖
CSV导出直接写一个单元测试类即可,首先引入测试相关的依赖
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>
CSV生成代码
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.vrs.domain.entity.TimePeriodDO;
import com.vrs.service.TimePeriodService;
import com.vrs.utils.TxtUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;import java.io.File;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.util.List;/*** 可预定时间段id CSV 导出** @Author dam* @create 2025/1/12 15:06*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {VrsVenueApplication.class})
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ReserveTestCSVGenerateTest {@Autowiredprivate TimePeriodService timePeriodService;/*** csv地址*/private final String csvPath = Paths.get("").toAbsolutePath().getParent().getParent() + File.separator + "tmp" + File.separator + "场馆预定时间段.csv";@Testpublic void generate() throws Exception {StringBuilder stringBuilder = new StringBuilder();QueryWrapper<TimePeriodDO> queryWrapper = new QueryWrapper<>();// 只查询在今天和今天之后的可预订时间段queryWrapper.ge("period_date", LocalDate.now());List<TimePeriodDO> timePeriodDOList = timePeriodService.list(queryWrapper);for (TimePeriodDO timePeriodDO : timePeriodDOList) {stringBuilder.append(timePeriodDO.getId() + "\n");}TxtUtil.write(new File(csvPath), stringBuilder.toString(), "utf-8");}
}
通过下面的注解为基于Spring Boot的应用程序提供全面的测试支持,包括依赖注入、应用上下文的配置以及Web环境的模拟等,这样我们才可以注入TimePeriodService来进行查询数据库等操作
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {VrsVenueApplication.class})
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
导出的CSV文件如下图所示

绑定CSV文件
最后一步是给请求绑定 CSV 文件

设置CSV文件路径

最后在请求的参数中使用,通过表头列名来绑定数据,使用方式为${列名}

配置不同的用户token
CSV 生成
import com.vrs.domain.entity.UserDO;
import com.vrs.service.UserService;
import com.vrs.utils.TxtUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;import java.io.File;
import java.nio.file.Paths;
import java.util.List;/*** 模拟用户数据生成** @Author dam* @create 2025/1/12 15:06*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {VrsAdminApplication.class})
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserTokenCSVGenerateTest {@Autowiredprivate UserService userService;/*** csv地址*/private final String csvPath = Paths.get("").toAbsolutePath().getParent().getParent() + File.separator + "tmp" + File.separator + "用户token.csv";@Testpublic void generate() throws Exception {StringBuilder stringBuilder = new StringBuilder();List<UserDO> userDOList = userService.list();for (UserDO userDO : userDOList) {// 登录并返回一个tokenstringBuilder.append(userService.handleLogin(userDO).getToken() + "\n");}TxtUtil.write(new File(csvPath), stringBuilder.toString(), "utf-8");}
}
绑定 CSV 文件


简单测试
完成上面的操作之后,启动压力测试即可,在查看结果树中,可以看到每个请求是否成功,响应结果是什么

接口错误的原因

在汇总报告中,可以查看压力测试的统计数据,例如接口调用时间的平均值、最小值、最大值,吞吐量……,这里异常那么高的原因是:用户已经购买过相应时间段或者时间段已经售罄

正式压测
测试环境
【测试机器】
- 名称:MacBook Pro 2023
- 尺寸:14英寸
- CPU:m2 pro丐版芯片(6个性能核心、4个能效核心)
- 内存:16GB
【服务启动方式】
为了模拟真实分布式环境下的性能表现,项目使用微服务方式启动
内存预热
为了在预定的时候可以快速查询,首先对需要使用到缓存进行预热,这里涉及的缓存有时间段信息、时间段库存、时间段位图
package com.vrs;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.vrs.constant.RedisCacheConstant;
import com.vrs.domain.entity.PartitionDO;
import com.vrs.domain.entity.TimePeriodDO;
import com.vrs.service.PartitionService;
import com.vrs.service.TimePeriodService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;import java.time.LocalDate;
import java.util.List;/*** 时间段预定缓存预热** @Author dam* @create 2025/1/12 15:06*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {VrsVenueApplication.class})
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TimePeriodCacheLoadTest {@Autowiredprivate TimePeriodService timePeriodService;@Autowiredprivate PartitionService partitionService;@Testpublic void generate() throws Exception {QueryWrapper<TimePeriodDO> queryWrapper = new QueryWrapper<>();// 只查询在今天和今天之后的可预订时间段queryWrapper.ge("period_date", LocalDate.now());List<TimePeriodDO> timePeriodDOList = timePeriodService.list(queryWrapper);for (TimePeriodDO timePeriodDO : timePeriodDOList) {timePeriodService.getTimePeriodDOById(timePeriodDO.getId());PartitionDO partitionDO = partitionService.getPartitionDOById(timePeriodDO.getPartitionId());// 首先检测空闲场号缓存有没有加载好,没有的话进行加载timePeriodService.checkBitMapCache(String.format(RedisCacheConstant.VENUE_TIME_PERIOD_FREE_INDEX_BIT_MAP_KEY, timePeriodDO.getId()), timePeriodDO.getId(), partitionDO.getNum());// 其次检测时间段库存有没有加载好,没有的话进行加载timePeriodService.getStockByTimePeriodId(timePeriodDO.getId());}}
}
预热之后的缓存如下:

压力测试
测试参数如下:
- 线程数:4000
- 循环次数:10
- Ramp-Up时间
即模拟4000个用户进行场馆预定,每个用户分别发送20次预定请求,线程在20秒内启动完成

测试结果如下:

- 样本数量:总共有40,000个样本,这表示在测试期间进行了40,000次请求或操作。
- 响应时间:
- 平均值:6036毫秒,表示所有请求的平均响应时间。
- 中位数:6442毫秒,表示50%的请求响应时间低于此值。
- 90%百分位:7155毫秒,表示90%的请求响应时间低于此值。
- 95%百分位:7264毫秒,表示95%的请求响应时间低于此值。
- 99%百分位:7482毫秒,表示99%的请求响应时间低于此值。
- 最小值:2毫秒,表示最快的请求响应时间。
- 最大值:26692毫秒,表示最慢的请求响应时间。
- 异常率:7.84%的请求出现了异常,当然这里的异常是:时间段售罄、时间段过期之类的。
- 吞吐量:508.3次请求/秒,表示系统在测试期间每秒处理的请求数量。
- 网络流量:
- 接收速率:284.68 KB/sec,表示系统每秒接收的数据量。
- 发送速率:248.40 KB/sec,表示系统每秒发送的数据量。
Jmeter结构

相关文章:
Jmeter 动态参数压力测试时间段预定接口
🎯 本文档详细介绍了如何使用Apache JMeter进行压力测试,以评估预定接口在高并发场景下的性能表现。通过创建线程组模拟不同数量的用户并发请求,利用CSV文件动态配置时间段ID和用户token,确保了测试数据的真实性和有效性。文档中还…...
超大型集团合并报表数智管理转型
摘要:数字经济时代,数字化技术已成为驱动财务管理价值释放的重要引擎,数智化能力的提升是当前一流财务信息化建设的最新趋势。财务部门是企业的“数据交汇中心”和“信息加工中心”,通过对企业各项财务数据的分类、汇总和清晰呈现…...
[MCAL]Mcu配置
PostBuild: PreCompile: 选择时钟来源; 选择初始McuInitClock() 函数 电路手册里有晶振频率,如上所示;...
Qt基础项目篇——Qt版Word字处理软件
一、核心功能 本软件为多文档型程序,界面是标准的 Windows 主从窗口 拥有:主菜单、工具栏、文档显示区 和 状态栏。 所要实现的东西,均在下图了。 开发该软件,主要分为下面三个阶段 1)界面设计开发 多窗口 MDI 程序…...
算法刷题笔记——图论篇
这里写目录标题 理论基础图的基本概念图的种类度 连通性连通图强连通图连通分量强连通分量 图的构造邻接矩阵邻接表 图的遍历方式 深度优先搜索理论基础dfs 与 bfs 区别dfs 搜索过程深搜三部曲所有可达路径广度优先搜索理论基础广搜的使用场景广搜的过程 岛屿数量孤岛的总面积沉…...
Java空指针异常处理:判空、Optional与Assert解析
在Java编程中,空指针异常(NullPointerException)是最常见的运行时错误之一。本文将深入探讨三种处理空指针异常的方法:传统的判空检查、Java 8引入的Optional类以及使用断言(Assert)。通过代码示例和应用场…...
【vim】vim编辑器如何设置行号
vim编辑器如何设置行号 一、**临时设置行号**二、永久设置行号2.1. **用户配置文件方式(针对当前用户)**2.2. **全局配置文件方式(谨慎使用,会影响所有用户)** 在Vim中设置行号有以下两种常见的方法: 一、…...
MySQL可直接使用的查询表的列信息
文章目录 背景实现方案模板SQL如何查询列如何转大写如何获取字符位置如何拼接字段 SQL适用场景 背景 最近产品找来,想让帮忙出下表的信息,字段驼峰展示,每张表信息show create table全部展示,再逐个粘贴,有点太耗费时…...
在线宠物用品|基于vue的在线宠物用品交易网站(源码+数据库+文档)
|在线宠物用品交易网站 目录 基于springbootvue的在线宠物用品交易网站 一、前言 二、系统设计 三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取: 博主介绍:✌️大厂码农|毕设布道师&am…...
《安富莱嵌入式周报》第349期:VSCode正式支持Matlab调试,DIY录音室级麦克风,开源流体吊坠,物联网在军工领域的应用,Unicode字符压缩解压
周报汇总地址:嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 视频版: 《安富莱嵌入式周报》第349期:VSCode正式支持Matlab调试,DIY录音室级麦克风…...
使用LabVIEW的History功能实现队列数据的读取而不清空
在LabVIEW中,有多种方法可以读取队列中的数据而不清空它。使用 Dequeue Element 和 Enqueue Element 函数可以实现读取并重新插入数据回队列,但当需要处理大数据流或需要更动态的解决方案时,这种方法可能会变得繁琐。一个更高效的解决方案是利…...
电脑如何访问手机文件?
手机和电脑已经深深融入了我们的日常生活,无时无刻不在为我们提供服务。除了电脑远程操控电脑外,我们还可以在电脑上轻松地访问Android或iPhone手机上的文件。那么,如何使用电脑远程访问手机上的文件呢? 如何使用电脑访问手机文件…...
SpringBoot实现定时任务,使用自带的定时任务以及调度框架quartz的配置使用
SpringBoot实现定时任务,使用自带的定时任务以及调度框架quartz的配置使用 文章目录 SpringBoot实现定时任务,使用自带的定时任务以及调度框架quartz的配置使用一. 使用SpringBoot自带的定时任务(适用于小型应用)二. 使用调度框架…...
java上传图片功能实现
1 MinIO核心概念 下面介绍MinIO中的几个核心概念,这些概念在所有的对象存储服务中也都是通用的。 对象(Object) 对象是实际的数据单元,例如我们上传的一个图片。 存储桶(Bucket) 存储桶是用于组织对象的命…...
73,【5】BUUCTF WEB [网鼎杯 2020 玄武组]SSRFMe(未解出)
进入靶场 又是代码又是代码又是代码又是代码又是代码又是代码又是代码又是代码又是代码又是代码又是代码又是代码又是代码又是代码 <?php // 检查 URL 是否为内部 IP 地址 function check_inner_ip($url) {// 使用正则表达式检查 URL 格式是否以 http、https、gopher 或 d…...
【FreeRTOS 教程 一】任务结构体及其基础创建使用
目录 一、任务与协程的区别: (1)任务的特点: (2)协程的特点: (3)总结: 二、任务概述 : (1)任务状态: &…...
深入剖析 JVM 内存模型
前言: 下面分别介绍了新生代和老年代的不同收集器及其相关子类型,并附有示例代码和说明,感兴趣的朋友可以参考一下。 简介: 在 Java 虚拟机(JVM)的世界里,内存模型是其核心架构之一࿰…...
解决DeepSeek-R1模型在Cursor中使用报错的问题
在使用Cursor时,如果你尝试调用DeepSeek-R1模型,可能会遇到以下报错信息: {"error": {"message": "deepseek-reasoner does not support successive user or assistant messages (messages[1] and messages[2] in …...
ASP.NET Core 6.0 如何处理丢失的 Startup.cs 文件
介绍 .NET 6.0 已经发布,ASP.NET Core 6.0 也已发布。其中有不少变化让很多人感到困惑。例如,“谁动了我的奶酪”,它在哪里Startup.cs?在这篇文章中,我将深入研究这个问题,看看它移动到了哪里以及其他变化。…...
Java如何向http/https接口发出请求
用Java发送web请求所用到的包都在java.net下,在具体使用时可以用如下代码,你可以把它封装成一个工具类 import javax.net.ssl.*; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.Outpu…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...
以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...
