商品列表接口 Sentinel 与 JMeter快速入门

YangeIT大约 7 分钟

商品列表接口 Sentinel 与 JMeter快速入门

1. 目标

只针对 product-service 中的商品列表接口完成下面两件事:

  • 任务3:接入 Sentinel,并完成限流、降级规则本地验证

  • 任务4:使用 JMeter 对商品列表接口压测,验证规则有效性,并输出压测报告


2. 测试接口

当前项目中的商品列表接口:

GET /api/product/{pageNum}/{pageSize}

示例:

http://localhost:9001/api/product/1/10?order=1

支持的主要查询参数:

  • keyword
  • brandId
  • category1Id
  • category2Id
  • category3Id
  • order

其中:

  • order=1 综合排序
  • order=2 价格升序
  • order=3 价格降序

3. 项目环境

当前项目关键信息:

  • 服务名:product-service
  • 端口:9001
  • Sentinel Dashboard 版本:sentinel-dashboard-1.8.1.jar
  • JMeter 版本:apache-jmeter-5.4.1.zip

4. Sentinel 接入

4.1 增加依赖

修改 product-service/pom.xml,加入:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-annotation-aspectj</artifactId>
</dependency>

4.2 修改配置

product-service/src/main/resources/application.yml 中增加:

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719
      eager: true

关键参数:

  • dashboard: localhost:8080:Sentinel 控制台地址
  • port: 8719:客户端通信端口
  • eager: true:服务启动时提前初始化 Sentinel

5. 启动 Sentinel Dashboard

启动命令:

java --add-opens java.base/java.lang=ALL-UNNAMED -Dserver.port=8080 -jar sentinel-dashboard-1.8.1.jar

访问地址:

http://localhost:8080

默认账号密码:

sentinel
sentinel

6. 给商品列表接口增加 Sentinel 资源

建议资源名:

product:list

6.1 新建 blockHandler

新建类:

com.zx.product.sentinel.ProductSentinelBlockHandler

代码:

package com.zx.product.sentinel;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.github.pagehelper.PageInfo;
import com.zx.domain.dto.h5.ProductSkuDto;
import com.zx.domain.entity.product.ProductSku;
import com.zx.domain.vo.common.Result;

import java.util.ArrayList;

public class ProductSentinelBlockHandler {

    public static Result<PageInfo<ProductSku>> findByPageBlock(Integer pageNum,
                                                               Integer pageSize,
                                                               ProductSkuDto dto,
                                                               BlockException ex) {
        return Result.build(PageInfo.of(new ArrayList<>()), 429, "商品列表访问过于频繁,请稍后再试");
    }
}

6.2 新建 fallback

新建类:

com.zx.product.sentinel.ProductSentinelFallbackHandler

代码:

package com.zx.product.sentinel;

import com.github.pagehelper.PageInfo;
import com.zx.domain.dto.h5.ProductSkuDto;
import com.zx.domain.entity.product.ProductSku;
import com.zx.domain.vo.common.Result;

import java.util.ArrayList;

public class ProductSentinelFallbackHandler {

    public static Result<PageInfo<ProductSku>> findByPageFallback(Integer pageNum,
                                                                  Integer pageSize,
                                                                  ProductSkuDto dto,
                                                                  Throwable throwable) {
        return Result.build(PageInfo.of(new ArrayList<>()), 500, "商品列表接口降级返回");
    }
}

6.3 修改 ProductController

给商品列表接口加 @SentinelResource

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.zx.product.sentinel.ProductSentinelBlockHandler;
import com.zx.product.sentinel.ProductSentinelFallbackHandler;

@Operation(summary = "商品列表接口")
@GetMapping("/{pageNum}/{pageSize}")
@SentinelResource(
        value = "product:list",
        blockHandlerClass = ProductSentinelBlockHandler.class,
        blockHandler = "findByPageBlock",
        fallbackClass = ProductSentinelFallbackHandler.class,
        fallback = "findByPageFallback"
)
public Result<PageInfo<ProductSku>> findByPage(@PathVariable Integer pageNum,
                                               @PathVariable Integer pageSize,
                                               ProductSkuDto dto) {
    PageInfo<ProductSku> pageInfo = productService.findByPage(pageNum, pageSize, dto);
    return Result.build(pageInfo, ResultCodeEnum.SUCCESS);
}

7. 注册资源到 Sentinel

启动 product-service 后,先访问一次接口:

http://localhost:9001/api/product/1/10?order=1

然后到 Sentinel Dashboard 查看是否出现资源:

product:list

8. Sentinel 规则配置

本次只配两类规则:

  • 限流规则
  • 降级规则

8.1 限流规则

资源名:

product:list

推荐参数:

参数项
阈值类型QPS
单机阈值3
流控模式直接
控制效果快速失败

含义:

  • 每秒最多通过 3 个商品列表请求
  • 超过后直接返回 blockHandler 中的结果
image
image

8.2 降级规则

image
image

资源名:

product:list

推荐参数:

参数项
熔断策略慢调用比例
最大 RT200 ms
比例阈值0.5
最小请求数5
统计时长10000 ms
熔断时长10 s

说明:

  • 10 秒内请求数至少 5 次
  • 且慢调用比例达到 50%
  • 则接口熔断 10 秒

如果本地接口太快,不容易触发降级,可以临时在 ProductServiceImpl.findByPage() 中加入:

   //随机睡0-400ms
        try {
            Thread.sleepnew Random().nextInt(400));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }  

仅用于本地验证,验证完可删除。


9. Sentinel 本地验证

9.1 验证限流

  1. 配置 product:list 限流规则
  2. 快速多次访问:
http://localhost:9001/api/product/1/10?order=1

预期结果:

  • 部分请求正常
  • 部分请求返回业务码 429

9.2 验证降级

  1. 临时增加 Thread.sleep(300)
  2. 配置 product:list 的降级规则
  3. 使用 JMeter 连续压测

预期结果:

  • 前几次请求正常
  • 达到慢调用比例阈值后,请求会被快速拒绝
image
image

10. JMeter 压测

10.1 启动 JMeter

解压 apache-jmeter-5.4.1.zip,进入 bin 目录,运行:

jmeter.bat

10.2 制作 .jmx

建议保存文件名:

product-list-test.jmx

10.3 用户定义变量

在测试计划中增加“用户定义的变量”:

变量名
protocolhttp
hostlocalhost
port9001
pageNum1
pageSize10

10.4 HTTP 请求默认值

增加“HTTP请求默认值”:

字段
协议${protocol}
服务器名称或IP${host}
端口号${port}

10.5 线程组

新增一个线程组,命名:

商品列表接口压测

推荐参数:

参数项
线程数50
Ramp-Up时间10
循环次数20

10.6 HTTP 请求

在线程组下新增 HTTP 请求:

字段
方法GET
路径/api/product/${pageNum}/${pageSize}

参数区填写:

NameValue
order1

如果你想固定某个分类,也可以增加:

NameValue
category3Id1
image
image

10.7 监听器

建议添加:

  • 察看结果树
  • 聚合报告
  • 汇总报告

正式压测时建议关闭“察看结果树”,避免占用太多内存。


11. 压测步骤

11.1 连通性测试

先把线程组改成:

参数项
线程数1
Ramp-Up时间1
循环次数1

确认:

  • 接口能返回 200
  • 响应体业务码为 200

11.2 基线压测

恢复线程组参数:

参数项
线程数50
Ramp-Up时间10
循环次数20

记录这些指标:

  1. Average 平均响应时间 单位:ms(毫秒) 定义:所有请求响应耗时的算术平均值,代表接口整体平均快慢。 缺陷:容易被极端慢请求掩盖真实体验,不能单独作为性能依据。

  2. 90% Line 90分位响应时间 单位:ms(毫秒) 定义:全部请求耗时从小到大排序后,90% 的请求耗时都小于等于该值,仅10%请求更慢。 代表绝大多数用户真实访问体验,是压测核心参考指标。

  3. Throughput 吞吐量 单位:req/sec(QPS,每秒请求数);文件/上传场景为 KB/sec 定义:单位时间内服务器成功处理的请求总量,代表服务扛并发、扛流量的能力。 压力到达瓶颈后,吞吐量会不升反降。

  4. Error % 错误率 单位:百分比 % 定义:失败请求数量占全部请求的比例,失败包含超时、连接失败、接口异常、断言不通过等。 直接衡量服务稳定性,标准压测要求错误率尽量为0。

11.3 Sentinel 验证压测

先打开 product:list 的限流规则和降级规则,再运行相同压测。

观察:

  • 错误率是否上升
  • 是否出现业务码 429
  • 是否出现降级返回

12. 性能优化建议

商品列表接口当前逻辑是:

  1. 先查 Product
  2. 收集商品 id
  3. 再查 ProductSku

因此可以从这几个方向优化:

12.1 限制分页大小

建议限制:

pageSize <= 40 或 50

12.2 对热点列表条件做缓存

例如下面这种常见请求:

/api/product/1/10?order=1

可以增加 Redis 缓存。

推荐 key:

product:list:{pageNum}:{pageSize}:{order}:{category1Id}:{category2Id}:{category3Id}:{brandId}:{keyword}

12.3 增加数据库索引

建议检查:

  • product.category1_id
  • product.category2_id
  • product.category3_id
  • product.brand_id
  • product.status
  • product.audit_status
  • product.is_deleted
  • product_sku.product_id

13. 导出 JMeter 报告

命令:

jmeter.bat -n -t D:\javasoftware\ChromeDriver\product-list-test.jmx -l D:\javasoftware\ChromeDriver\result.jtl -e -o D:\javasoftware\ChromeDriver\html-report
image
image

生成后打开:

D:\report\html-report\index.html
image
image

重点截图:

  • Average Response Time
  • Throughput
  • Error %
image
image

14. 报告模板

14.1 接口信息

项目内容
接口名称商品列表接口
请求方式GET
请求路径/api/product/{pageNum}/{pageSize}
服务端口9001

14.2 Sentinel 规则

资源名规则类型参数
product:list限流QPS=3
product:list降级慢调用比例,RT=200ms,比例=0.5,熔断10s

14.3 压测结果

阶段Average90% LineThroughputError %
基线压测
开启 Sentinel 后
优化后压测

14.4 实验结论

可以直接围绕这几句写:

  1. 商品列表接口已经完成 Sentinel 接入
  2. 通过限流规则可以保护商品列表接口不被高并发流量冲垮
  3. 通过降级规则可以在慢调用比例过高时自动熔断
  4. 使用 JMeter 可以验证接口性能变化和 Sentinel 规则生效情况
  5. 对分页大小、热点条件缓存、数据库索引进行优化后,接口性能会进一步提升