淘客app的接口性能测试:基于JMeter的高并发场景模拟与优化
淘客app的接口性能测试:基于JMeter的高并发场景模拟与优化
大家好,我是阿可,微赚淘客系统及省赚客APP创始人,是个冬天不穿秋裤,天冷也要风度的程序猿!
淘客APP的核心接口(如商品搜索、返利计算、订单同步)在大促期间需承受每秒数千次的请求冲击,接口性能直接决定用户体验与业务稳定性。基于JMeter的高并发场景测试,能提前暴露接口瓶颈(如数据库慢查询、线程池耗尽),本文结合淘客APP实际业务,从测试场景设计、脚本开发、性能瓶颈定位到优化落地,提供完整技术方案,包含JMeter脚本与Java优化代码。
一、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(依赖httpclient
与jmeter-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
)未走索引,优化方案:
- 添加联合索引:
CREATE INDEX idx_goods_title_status ON taoke_goods(title(50), status, rebate_rate DESC);
- 重写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开发者团队,转载请注明出处!