淘客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、请求体、租户密钥
@Override
public 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;
}
@Override
public 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 {
@Resource
private RedisTemplate<String, GoodsDTO> redisTemplate;
@Resource
private 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开发者团队,转载请注明出处!