极客时间架构师训练营 - week7 - 作业 1
发布于: 2020 年 07 月 22 日
性能压测的时候,随着并发压力的增加,系统响应时间和吞吐量如何变化,为什么?
答:随着并发压力的增加,系统的响应时间在一定时间内呈现线性增加,当超过系统能承受的最大负载点之后,系统的响应时间会呈指数式增加,如下图所示,当并发压力进一步增加的时候,超出系统所能承受的最大量的时候,系统随时可能进入崩溃状态。
系统的整体吞吐量与并发数的关系如下图所示。在一定范围内,随着并发数的增加,系统的整体吞吐量线性增加(图中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@RestControllerpublic 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 */@Datapublic 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@AllArgsConstructorpublic 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@AllArgsConstructorpublic 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@Servicepublic 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"}
划线
评论
复制
发布于: 2020 年 07 月 22 日阅读数: 94
版权声明: 本文为 InfoQ 作者【jjn0703】的原创文章。
原文链接:【http://xie.infoq.cn/article/a213b260dd3771bc9b9daaf1d】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
jjn0703
关注
Java工程师/终身学习者 2018.03.26 加入
USTC硕士/健身健美爱好者/Java工程师.
评论