写点什么

极客时间架构师训练营 - week7 - 作业 1

用户头像
jjn0703
关注
发布于: 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
@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"
}



发布于: 2020 年 07 月 22 日阅读数: 94
用户头像

jjn0703

关注

Java工程师/终身学习者 2018.03.26 加入

USTC硕士/健身健美爱好者/Java工程师.

评论

发布
暂无评论
极客时间架构师训练营 - week7 - 作业 1