利用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都耗用的任务
- 该方法执行100ms左右, 不会去极限压榨cpu, 只是会让线程计算benchmarkTaskTime的时间
- 但是这个时间也算上了不被分配cpu的时间, 不是纯cpu计算的时间
- 可以用来模拟即耗用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

浙公网安备 33010602011771号