利用JMeter对SpringBoot 同步与异步请求进行压测

同步与异步

引用一段书本里面的话来简单介绍一下同步与异步

同步, 可以理解为在执行完一个函数或方法之后, 一直等待系统返回值或消息, 这时程序是出于阻塞的, 只有接收到返回的值或消息后才往下执行其他的命令.

异步, 执行完函数或方法后, 不必阻塞性地等待返回值或消息, 只需要向系统委托一个异步过程, 那么当系统接收到返回值或消息时, 系统会自动触发委托的异步过程, 从而完成一个完整的流程.

Spring Web工程中的异步

Spring的Web工程原生也支持了异步编程
Spring提供了DeferredResult类与@Asycn注解来支持两种不同的异步编程模式

BaseResult返回结构体

@Data
public class BaseResult<T> {

    private Integer code;

    private String msg;

    private T data;

    public static <T> BaseResult<T> success(T obj) {
        BaseResult<T> baseResult = new BaseResult<>();
        baseResult.setCode(ResultEnum.SUCCESS.getCode());
        baseResult.setMsg(ResultEnum.SUCCESS.getMsg());
        baseResult.setData(obj);
        return baseResult;
    }

    public static <T> BaseResult<T> success() {
        return success(null);
    }

    public static <T> BaseResult<T> error(Integer code, String msg) {
        BaseResult<T> baseResult = new BaseResult<>();
        baseResult.setCode(code);
        baseResult.setMsg(msg);
        baseResult.setData(null);
        return baseResult;
    }

}

DeferredResult

@GetMapping("/deferredResultAsync")
public DeferredResult<BaseResult<String>> deferredResultAsync(@RequestParam(required = false, defaultValue = "false") boolean isCpuTask) {
    DeferredResult<BaseResult<String>> deferredResult = new DeferredResult<>();
    executors.submit(() -> deferredResult.setResult(benchmarkService.mockTask("deferredResultAsync", isCpuTask)));
    return deferredResult;
}

@Async注解

需要在Configuration或Boot类上加上@EnableAsync注解, 以开启异步注解的支持
Controller方法

@GetMapping("/listenableFutureAsync")
public ListenableFuture<BaseResult<String>> listenableFutureAsync(@RequestParam(required = false, defaultValue = "false") boolean isCpuTask) {
    return benchmarkService.listenableFutureAsync(isCpuTask);
}

Service方法

@Async
public ListenableFuture<BaseResult<String>> listenableFutureAsync(boolean isCpuTask) {
    return AsyncResult.forValue(mockTask("listenableFutureAsync", isCpuTask));
}

任务类型

IO密集型任务

IO密集型任务指的是那些耗用IO的操作, 比如文件读写, 读取外部数据库, 调用外部接口等, 过程是阻塞的
下面来模拟调用外部接口来mock一个IO密集型任务

private BaseResult<String> mockIOTask(String mark) {
    try {
        TimeUnit.MILLISECONDS.sleep(benchmarkTaskTime);
    } catch (InterruptedException e) {
        // do nothing
        LOGGER.error("Mock IO Task Error", e);
    }
    return BaseResult.success(mark);
}

Cpu密集型任务

Cpu密集型任务指的是那些需要操作Cpu进行一定数据计算的任务, 比如加减乘除, 去重, 词序统计, 排序等
下面通过一段for循环计算来mock一个Cpu密集型任务

private BaseResult<String> mockCpuTask(String mark) {
    int num = 0;
    int firstCycle = 500;
    int secondCycle = firstCycle * 100;
    for (int i = 0; i < firstCycle; i++) {
        for (int j = 0; j < secondCycle; j++) {
            num += i * j;
            if (num > 10000) {
                num += i;
            }
        }
    }
    // 欺骗编译器, 防止优化, 因为前面的代码计算的数据都没有被依赖
    LOGGER.debug("num:{}", num);
    return BaseResult.success(mark);
}

IO和Cpu都耗用的任务

IO和Cpu都耗用的任务类似于那些从数据库中取出数据, 然后进行计算操作的场景

下面通过时间差来mock一个IO和Cpu都耗用的任务

  1. 该方法执行100ms左右, 不会去极限压榨cpu, 只是会让线程计算benchmarkTaskTime的时间
  2. 但是这个时间也算上了不被分配cpu的时间, 不是纯cpu计算的时间
  3. 可以用来模拟即耗用io, 也耗用cpu的任务
private BaseResult<String> mockIOAndCpuTask(String mark) {
    long startTime = System.currentTimeMillis();
    // 占同cpu benchmarkTaskTime ms
    while ((System.currentTimeMillis() - startTime) <= benchmarkTaskTime) {
        Map<String, String> map = new HashMap<>();
        map.put(UUID.randomUUID().toString(), UUID.randomUUID().toString());
        map.put(UUID.randomUUID().toString(), UUID.randomUUID().toString());
        // 欺骗编译器, 不输出到控制台
        // 防止因为后续的代码不对前面造成依赖, 而导致被优化
        LOGGER.debug(JsonUtils.toJson(map));
    }
    LOGGER.info("Cpu Task Mark:{}", mark);
    return BaseResult.success(mark);
}

环境说明

  • Cpu i5-9400 6c6t
  • 内存 16GB
  • 操作系统 mac OS Mojave 10.14.5

笔者是在本地使用JMeter对本地的SpringBoot进程进行压测的, 测试结果可能受到影响, 但也能在一定程度上反映问题

JMeter压测参数

todo

JMeter压测结果

todo

Tomcat线程数500 压测结果

todo

Tomcat线程数1000 压测结果

todo

结果分析

todo

posted @ 2020-12-06 10:08  梅子酒zZ  阅读(1169)  评论(0)    收藏  举报