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

发布于:2025-09-15 ⋅ 阅读:(20) ⋅ 点赞:(0)

淘客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、请求体、租户密钥
    @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(依赖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 {

    @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开发者团队,转载请注明出处!