当前位置: 首页 > news >正文

淘客app的接口性能测试:基于JMeter的高并发场景模拟与优化

淘客app的接口性能测试:基于JMeter的高并发场景模拟与优化

大家好,我是阿可,微赚淘客系统及省赚客APP创始人,是个冬天不穿秋裤,天冷也要风度的程序猿!

淘客APP的核心接口(如商品搜索、返利计算、订单同步)在大促期间需承受每秒数千次的请求冲击,接口性能直接决定用户体验与业务稳定性。基于JMeter的高并发场景测试,能提前暴露接口瓶颈(如数据库慢查询、线程池耗尽),本文结合淘客APP实际业务,从测试场景设计、脚本开发、性能瓶颈定位到优化落地,提供完整技术方案,包含JMeter脚本与Java优化代码。
淘客app

一、JMeter测试场景设计:贴合淘客业务高并发场景

淘客APP的高并发集中在“商品查询”(用户搜索返利商品)与“订单同步”(大促后批量同步淘宝联盟订单),需针对这两类场景设计测试用例,模拟真实流量特征。

1.1 商品查询接口测试场景(TPS导向)

模拟1000用户同时搜索商品,持续5分钟,重点监控接口响应时间(RT)、吞吐量(TPS)与错误率,JMeter测试计划核心配置如下:

  • 线程组设置:线程数1000, Ramp-Up时间60秒(逐步加压避免瞬间冲击),循环次数-1(持续运行),调度器时长300秒(5分钟)
  • 取样器配置(HTTP请求):
    • 协议:HTTPS
    • 服务器名称:api.juwatech.cn
    • 端口号:443
    • 路径:/taoke/v1/goods/search
    • 方法:POST
    • 请求体(JSON格式,动态参数化商品关键词):
    {"keyword": "${goodsKeyword}","page": 1,"pageSize": 20,"sortType": "rebateRateDesc"
    }
    
  • 参数化配置(CSV数据文件设置):
    • 文件名:goods_keywords.csv(包含“口红”“运动鞋”“母婴用品”等500个真实搜索词)
    • 变量名:goodsKeyword
    • 分隔符:逗号
    • 循环读取:True(确保1000线程有足够参数)
  • 断言配置(JSON断言):
    • 断言路径:$.code
    • 预期值:200(接口正常返回码)
  • 监听器配置:聚合报告、Summary Report、TPS曲线(jp@gc - Transactions per Second)、响应时间曲线(jp@gc - Response Time Over Time)

1.2 订单同步接口测试场景(数据量导向)

模拟大促后批量同步订单,单次请求携带100条订单数据,测试200线程并发下接口的处理能力,JMeter脚本核心配置差异点:

  • 取样器请求体(批量订单数据,通过JMeter函数生成动态订单号):
    {"tenantId": "tenant_1001","orders": [{"orderId": "T${__Random(10000000,99999999,)}","taobaoOrderId": "${__RandomString(16,0123456789abcdef,)}","goodsId": "${__Random(10000,99999,)}","rebateAmount": "${__Random(1.00,99.99,)}","payTime": "${__time(yyyy-MM-dd HH:mm:ss,)}"}// 省略99条订单数据(通过JMeter循环控制器生成)]
    }
    
  • 定时器配置:固定定时器(100ms),模拟订单同步的批次间隔
  • 监听器新增:jp@gc - Active Threads Over Time(监控活跃线程数)、jp@gc - Bytes Throughput Over Time(监控流量吞吐量)

二、JMeter脚本进阶:自定义Java请求与结果处理

针对淘客APP的加密接口(如用户登录、支付回调),需通过JMeter自定义Java请求实现签名生成,同时通过后置处理器过滤无效结果。

2.1 自定义Java请求(实现签名生成)

淘客APP接口要求请求头携带X-Sign签名(算法:MD5(请求体+租户密钥+时间戳)),通过cn.juwatech.jmeter.request.TaokeSignRequest实现:

package cn.juwatech.jmeter.request;import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient;
import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.http.client.fluent.Request;
import org.apache.http.entity.ContentType;import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.util.Base64;public class TaokeSignRequest extends AbstractJavaSamplerClient {// 定义参数:接口URL、请求体、租户密钥@Overridepublic Arguments getDefaultParameters() {Arguments args = new Arguments();args.addArgument("apiUrl", "https://api.juwatech.cn/taoke/v1/order/sync");args.addArgument("requestBody", "{}");args.addArgument("tenantSecret", "juwatech_tenant_1001_secret");return args;}@Overridepublic SampleResult runTest(JavaSamplerContext context) {SampleResult result = new SampleResult();result.sampleStart(); // 开始计时String apiUrl = context.getParameter("apiUrl");String requestBody = context.getParameter("requestBody");String tenantSecret = context.getParameter("tenantSecret");String timestamp = String.valueOf(System.currentTimeMillis() / 1000);try {// 1. 生成签名String sign = generateSign(requestBody, tenantSecret, timestamp);// 2. 发送HTTP请求String response = Request.Post(apiUrl).bodyString(requestBody, ContentType.APPLICATION_JSON).addHeader("X-Timestamp", timestamp).addHeader("X-Sign", sign).execute().returnContent().asString();// 3. 设置测试结果result.setSuccessful(true);result.setResponseData(response, "UTF-8");result.setResponseCodeOK();} catch (Exception e) {result.setSuccessful(false);result.setResponseMessage(e.getMessage());} finally {result.sampleEnd(); // 结束计时}return result;}// 签名生成逻辑private String generateSign(String requestBody, String secret, String timestamp) throws Exception {String signSource = requestBody + secret + timestamp;MessageDigest md = MessageDigest.getInstance("MD5");byte[] digest = md.digest(signSource.getBytes("UTF-8"));return Base64.getEncoder().encodeToString(digest);}
}

将该类打包为JAR(依赖httpclientjmeter-core),放入JMeter的lib/ext目录,重启后在“Java请求”取样器中选择该类即可使用。

2.2 后置处理器(JSON提取器)

从商品查询接口响应中提取“商品ID”,用于后续“商品详情查询”接口的关联测试,配置如下:

  • 引用名称:goodsId
  • JSON路径表达式$.data.list[0].goodsId(提取第一个商品的ID)
  • 匹配数字:0(随机提取一个)
  • 默认值:-1(提取失败时的默认值)

三、性能瓶颈定位与优化落地

通过JMeter测试,淘客APP的商品查询接口在1000并发下出现RT超过2秒、TPS仅150的瓶颈,结合Arthas(Java诊断工具)定位到数据库慢查询与Redis缓存未命中问题,针对性优化如下。

3.1 数据库慢查询优化(添加索引+SQL重写)

通过Arthas发现商品查询接口的SQL(SELECT * FROM taoke_goods WHERE title LIKE '%keyword%' AND status=1 ORDER BY rebate_rate DESC)未走索引,优化方案:

  1. 添加联合索引
CREATE INDEX idx_goods_title_status ON taoke_goods(title(50), status, rebate_rate DESC);
  1. 重写SQL(使用全文索引替代LIKE模糊查询)
ALTER TABLE taoke_goods ADD FULLTEXT INDEX ft_idx_goods_title (title);
SELECT * FROM taoke_goods WHERE MATCH(title) AGAINST('keyword' IN BOOLEAN MODE) AND status=1 ORDER BY rebate_rate DESC;

3.2 Redis缓存优化(预热+过期时间动态调整)

通过cn.juwatech.cache.TaokeGoodsCache实现商品缓存预热与动态过期,减少数据库查询压力:

package cn.juwatech.cache;import cn.juwatech.goods.dto.GoodsDTO;
import cn.juwatech.goods.service.GoodsService;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;@Component
public class TaokeGoodsCache {@Resourceprivate RedisTemplate<String, GoodsDTO> redisTemplate;@Resourceprivate GoodsService goodsService;// 缓存key前缀private static final String GOODS_CACHE_KEY_PREFIX = "taoke:goods:search:";// 1. 缓存查询(优先从缓存获取,未命中则查库并更新缓存)public List<GoodsDTO> getGoodsFromCache(String keyword, Integer page, Integer pageSize) {String cacheKey = GOODS_CACHE_KEY_PREFIX + keyword + "_" + page + "_" + pageSize;List<GoodsDTO> goodsList = redisTemplate.opsForValue().get(cacheKey);if (goodsList == null) {// 缓存未命中,查库goodsList = goodsService.searchGoods(keyword, page, pageSize);// 动态设置过期时间:热门关键词(查询量高)缓存1小时,普通关键词缓存10分钟long expireTime = isHotKeyword(keyword) ? 3600 : 600;redisTemplate.opsForValue().set(cacheKey, goodsList, expireTime, TimeUnit.SECONDS);}return goodsList;}// 2. 缓存预热(每天凌晨3点预热热门关键词缓存)@Scheduled(cron = "0 0 3 * * ?")public void preloadGoodsCache() {// 获取TOP50热门搜索关键词(从数据库统计表获取)List<String> hotKeywords = goodsService.getHotSearchKeywords(50);for (String keyword : hotKeywords) {// 预热前3页数据for (int page = 1; page <= 3; page++) {List<GoodsDTO> goodsList = goodsService.searchGoods(keyword, page, 20);String cacheKey = GOODS_CACHE_KEY_PREFIX + keyword + "_" + page + "_20";redisTemplate.opsForValue().set(cacheKey, goodsList, 3600, TimeUnit.SECONDS);}}}// 判断是否为热门关键词(查询量前10%)private boolean isHotKeyword(String keyword) {Long searchCount = redisTemplate.opsForValue().increment("taoke:goods:search:count:" + keyword, 0);Long totalHotCount = redisTemplate.opsForValue().get("taoke:goods:search:hot:total_count");return searchCount != null && totalHotCount != null && searchCount >= totalHotCount * 0.1;}
}

优化后重新执行JMeter测试,商品查询接口在1000并发下RT降至300ms以内,TPS提升至800,错误率为0,完全满足大促期间的性能需求。

本文著作权归聚娃科技省赚客app开发者团队,转载请注明出处!


文章转载自:

http://RLY2ojfr.nbqwr.cn
http://RfIjcg2G.nbqwr.cn
http://yIVIehEw.nbqwr.cn
http://O2ddyGwu.nbqwr.cn
http://T1s3MAwu.nbqwr.cn
http://lNcYt7As.nbqwr.cn
http://8Sufy3nO.nbqwr.cn
http://hZoRWBWW.nbqwr.cn
http://GPEyEOJr.nbqwr.cn
http://8UpuK9X7.nbqwr.cn
http://HZV0nxZT.nbqwr.cn
http://Z3n040cG.nbqwr.cn
http://Lw3gNuAK.nbqwr.cn
http://7VYbYMlR.nbqwr.cn
http://kI76b3Fo.nbqwr.cn
http://3QsO74J8.nbqwr.cn
http://njnJQRKD.nbqwr.cn
http://aUlpAe6p.nbqwr.cn
http://Ot4wloXl.nbqwr.cn
http://r1ic3iKI.nbqwr.cn
http://2KrhIHGv.nbqwr.cn
http://SoIdhYfp.nbqwr.cn
http://5xUMhJ8j.nbqwr.cn
http://oa91LQme.nbqwr.cn
http://3xatYHNI.nbqwr.cn
http://LPADFPko.nbqwr.cn
http://go9LX1fg.nbqwr.cn
http://VOLsbOvo.nbqwr.cn
http://7YOOmeWT.nbqwr.cn
http://OeJ6a31R.nbqwr.cn
http://www.dtcms.com/a/383817.html

相关文章:

  • C++ 继承:从概念到实战的全方位指南
  • Python中全局Import和局部Import的区别及应用场景对比
  • S16 赛季预告
  • 【硬件-笔试面试题-95】硬件/电子工程师,笔试面试题(知识点:RC电路中的时间常数)
  • synchronized锁升级的过程(从无锁到偏向锁,再到轻量级锁,最后到重量级锁的一个过程)
  • Altium Designer(AD)自定义PCB外观颜色
  • Flink快速上手使用
  • 安卓学习 之 选项菜单(OptionMenu)
  • CKA04--storageclass
  • Dask read_csv未指定数据类型报错
  • 【代码随想录算法训练营——Day11】栈与队列——150.逆波兰表达式求值、239.滑动窗口最大值、347.前K个高频元素
  • TruthfulQA:衡量语言模型真实性的基准
  • 继承与多态
  • Python爬虫实战:研究Pandas,构建新浪网股票数据采集和分析系统
  • 【从零开始】14. 数据评分与筛选
  • 正则表达式与文本三剑客(grep、sed、awk)基础与实践
  • JavaWeb--day5--请求响应分层解耦
  • 去卷积:用魔法打败魔法,让图像清晰
  • Java开发者LLM实战——LangChain4j最新版教学知识库实战
  • 算法 --- 哈希表
  • 【科研绘图系列】R语言绘制全球海洋温度对浮游生物分裂率影响的数据可视化分析
  • 141.环形链表
  • C++ 最短路SPFA
  • 一文读懂 Java 注解运行原理
  • Dify开发中系统变量(system)和用户变量(user)的区别
  • 扩散模型之(五)基于概率流ODE方法
  • 【代码模板】Linux内核模块带指针的函数如何返回错误码?(ERR_PTR(-ENOMEM)、IS_ERR(ent)、PTR_ERR(ent))
  • 查询 mysql中 所有的 非空记录字段
  • Spring Bean:不只是“对象”那么简单
  • 快速选中对象