- 性能压测的时候,随着并发压力的增加,系统响应时间和吞吐量如何变化,为什么?
答:随着并发压力的增加,系统的响应时间在一定时间内呈现线性增加,当超过系统能承受的最大负载点之后,系统的响应时间会呈指数式增加,如下图所示,当并发压力进一步增加的时候,超出系统所能承受的最大量的时候,系统随时可能进入崩溃状态。
系统的整体吞吐量与并发数的关系如下图所示。在一定范围内,随着并发数的增加,系统的整体吞吐量线性增加(图中 0-1 的区间),当到达一定数值之后,系统的整体吞吐量到达瓶颈(图中 1-2 的区间),再增加并发之后,系统的整体吞吐量呈现下滑的趋势,系统有随时崩溃的风险。
- 用你熟悉的编程语言写一个 web 性能压测工具,输入参数:URL,请求总次数,并发数。输出参数:平均响应时间,95% 响应时间。用这个测试工具以 10 并发、100 次请求压测 www.baidu.com。
答:采用 Java 语言实现,如下:
controller:
package com.example.performance.controller; import com.example.performance.entity.PerformanceEntity; import com.example.performance.entity.ResponseBean; import com.example.performance.service.PerformanceService; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * @author Jiang Jining * @date 2020/7/22 20:39 */ @CrossOrigin @RestController public class PerformanceController { @Resource private PerformanceService performanceService; @PostMapping(value = "/api/v1/performance/test") public ResponseBean<String> testPerformance(@RequestBody PerformanceEntity performanceEntity) { String s = performanceService.calculatePerformance(performanceEntity); return ResponseBean.success(s); } }
entity:
package com.example.performance.entity; import lombok.Data; /** * @author Jiang Jining * @date 2020/7/22 20:46 */ @Data public class PerformanceEntity { private String url; private Integer threadNum; }
package com.example.performance.entity; import com.alibaba.fastjson.JSON; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * @author Administrator */ @Data @NoArgsConstructor @AllArgsConstructor public class ResponseBean<T> implements Serializable { private static final long serialVersionUID = -3699842403600131110L; /** * 状态码 */ private int code; /** * 消息 */ private String message; /** * 结果数据 */ private T data; @Override public String toString() { return JSON.toJSONString(this); } public static <T> ResponseBean<T> success() { return new ResponseBean(ErrorEnum.SUCCESS.getErrorCode(), ErrorEnum.SUCCESS.getErrorMsg(), null); } public static <T> ResponseBean<T> success(T data) { return new ResponseBean(ErrorEnum.SUCCESS.getErrorCode(), ErrorEnum.SUCCESS.getErrorMsg(), data); } public static <T> ResponseBean<T> success(T data, String message) { return new ResponseBean(ErrorEnum.SUCCESS.getErrorCode(), message, data); } public static <T> ResponseBean<T> error(int code, String message) { return new ResponseBean(code, message, null); } public static ResponseBean error(String message) { return new ResponseBean(ErrorEnum.FAIL.getErrorCode(), message, null); } }
package com.example.performance.entity; /** * @author * @date 2018/9/18 */ public enum ErrorEnum { ERROR_10000(10000, "登录Token过期"), ERROR_10001(10001, "未登录"), ERROR_10002(10002, "账号在其他地方登陆"), SUCCESS(200, "成功"), FAIL(400, "失败"), UNAUTHORIZED(401, "认证失败"), PERMISSION_DENIED(402, "权限错误"), PARAMETER_ERROR(403, "参数错误"), NOT_FOUND(404, "接口不存在"), SERVER_ERROR(500, "服务器内部错误"), /** * 不推荐使用 */ E_400(400, "请求处理异常,请稍后再试"), ERROR_401(401, "账号权限不足"), ERROR_500(500, "系统内部错误"); /** * 错误码 */ private int errorCode; /** * 错误信息 */ private String errorMsg; ErrorEnum(int errorCode, String errorMsg) { this.errorCode = errorCode; this.errorMsg = errorMsg; } public int getErrorCode() { return errorCode; } public void setErrorCode(int errorCode) { this.errorCode = errorCode; } public String getErrorMsg() { return errorMsg; } public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; } }
service:
package com.example.performance.service; import com.example.performance.entity.PerformanceEntity; /** * @author Jiang Jining * @date 2020/7/22 20:44 */ public interface PerformanceService { String calculatePerformance(PerformanceEntity performanceEntity); }
package com.example.performance.service.impl; import cn.hutool.http.HttpUtil; import lombok.AllArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StopWatch; import java.time.LocalDate; import java.util.concurrent.Callable; /** * @author Jiang Jining * @date 2020/7/22 21:17 */ @Setter @Slf4j @AllArgsConstructor public class PerformanceTask implements Callable<Long> { private Integer currentNum; private String url; @Override public Long call() throws Exception { StopWatch stopWatch = new StopWatch(); stopWatch.start(); LocalDate localDate = LocalDate.of(2020, 1, 1); localDate = localDate.plusDays(currentNum); url += "/s?wd=" + localDate.toString(); String s = HttpUtil.get(url, 30_000); if (log.isDebugEnabled()) { log.debug(s); } stopWatch.stop(); if (log.isInfoEnabled()) { log.info("currentNum:{}, date:{}, total:{}", currentNum, localDate.toString(), stopWatch.getTotalTimeSeconds()); } return stopWatch.getTotalTimeMillis(); } }
package com.example.performance.service.impl; import com.example.performance.entity.PerformanceEntity; import com.example.performance.service.PerformanceService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; /** * @author Jiang Jining * @date 2020/7/22 20:45 */ @Slf4j @Service public class PerformanceServiceImpl implements PerformanceService { private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 10, 1000L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), new BasicThreadFactory.Builder().namingPattern("performance-pool-%d").daemon(true).build()); @Override public String calculatePerformance(PerformanceEntity performanceEntity) { Objects.requireNonNull(performanceEntity); Integer threadNum = performanceEntity.getThreadNum(); Objects.requireNonNull(threadNum); String url = performanceEntity.getUrl(); Objects.requireNonNull(url); AtomicInteger atomicInteger = new AtomicInteger(0); List<Long> timeConsumeList = new ArrayList<>(threadNum); List<PerformanceTask> futureList = new ArrayList<>(threadNum); List<Future<Long>> tempList = new ArrayList<>(threadNum); for (int i = 0; i < threadNum; i++) { PerformanceTask performanceTask = new PerformanceTask(atomicInteger.addAndGet(1), performanceEntity.getUrl()); Future<Long> timeConsumeFuture = threadPoolExecutor.submit(performanceTask); tempList.add(timeConsumeFuture); futureList.add(performanceTask); } try { threadPoolExecutor.invokeAll(futureList, 1, TimeUnit.MINUTES); } catch (InterruptedException e) { if (log.isErrorEnabled()) { log.error(e.getMessage()); } } tempList.forEach(longFuture -> { try { Long aLong = longFuture.get(); timeConsumeList.add(aLong); } catch (InterruptedException | ExecutionException exception) { if (log.isErrorEnabled()) { log.error(exception.getMessage()); } } }); timeConsumeList.sort(Long::compare); long totalConsume = 0L; int size = timeConsumeList.size(); for (Long aLong : timeConsumeList) { totalConsume += aLong; } int ninetyFifthIndex = (int) (size * 0.95); return "95%响应时间:" + timeConsumeList.get(ninetyFifthIndex - 1) + "ms, 平均响应时间:" + totalConsume * 1.0 / size + "ms"; } }
测试结果:
请求参数:
{"url":"http://www.baidu.com", "threadNum": 10}
计算结果:
{ "code": 200, "message": "成功", "data": "95%响应时间:413ms, 平均响应时间:135.5ms" }
请求参数:
{"url":"http://www.baidu.com", "threadNum": 100}
计算结果:
{ "code": 200, "message": "成功", "data": "95%响应时间:818ms, 平均响应时间:600.38ms" }