写点什么

第七周课后作业

用户头像
iHai
关注
发布于: 2020 年 07 月 23 日

作业一:

性能压测的时候,随着并发压力的增加,系统响应时间和吞吐量如何变化,为什么?

简单回答一下:

课程上已明确指出随着并发压力的增加,系统响应时间会经历a、b、c、d点。而吞吐量和系统响应时间有关。

至于为什么会这样变化,个人觉得可以从课程中“系统性能优化的分层思想”列出的点来逐一分析。



作业二:

用你熟悉的编程语言写一个 web 性能压测工具,输入参数:URL,请求总次数,并发数。输出参数:平均响应时间,95% 响应时间。用这个测试工具以 10 并发、100 次请求压测 www.baidu.com

题目理解:

95%响应时间(95%percentile )在JMeter中解释是:95% of the samples took NO MORE THAN this time. The 5% remaining samples took at least as long as this.

另一个角度是:一组数由小到大进行排列,找到总长度第95%个位置对应的数值。



大概实现思路是:

1、使用线程池,设置n个核心线程,加上CountDownLatch来模拟n个并发请求。

2、请求所花时间的保存需要注意线程安全,个人目前使用的是ConcurrentHashMap来记录。



具体代码如下:

//定义一个接口
public interface WebRequestPressureTest {

/**
* 测试Get请求
*
* @param getRequestUrl GET请求的url
* @param concurrency 并发数
* @param requestNumber 请求总数
* @return
*/
TestResult testRequest(String getRequestUrl, int concurrency, int requestNumber);
}

//描述请求测试结果
@Data
public class TestResult {

/**
* 平均响应时间, 单位为毫秒
*/
int averageResponseTime;

/**
* 95%响应时间, 单位为毫秒
*/
int ninetyFivePercentile;
}



接口实现

public class CommonWebRequestPressureTest implements WebRequestPressureTest {

//实现接口
@Override
public TestResult testRequest(String getRequestUrl, int concurrency, int requestNumber) {
//获取所有请求花费的时间
List<Long> intervals = request(getRequestUrl, concurrency, requestNumber);
//计算平均响应时间和95%的响应时间
return getTestResultWithRequestIntervals(intervals);
}


private List<Long> request(String getRequestUrl, int concurrency, int requestNumber) {
ExecutorService executorService = Executors.newFixedThreadPool(concurrency);
ConcurrentMap<Integer, Long> requestIntervalMap = new ConcurrentHashMap<>(requestNumber);
CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch endLatch = new CountDownLatch(requestNumber);
//添加请求
for (int i = 0; i < requestNumber; i++) {
executorService.submit(new RequestRunner(startLatch, endLatch, i, requestIntervalMap, getRequestUrl));
}

//开始请求
startLatch.countDown();
//等待所有请求完成
try {
endLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}

executorService.shutdown();
//返回所有请求时间
return new ArrayList<>(requestIntervalMap.values());
}

private TestResult getTestResultWithRequestIntervals(List<Long> intervals) {
//从小到大排序,用于计算95%的响应时间
intervals.sort(Comparator.comparing(Long::longValue));
Long[] intervalsArray = intervals.toArray(new Long[0]);
//计算95%的响应时间
int ninetyFiveIndex = (int) Math.round(intervals.size() * 0.95) - 1;
int ninetyFiveInterval = Math.toIntExact(intervalsArray[ninetyFiveIndex]);
//计算平均响应时间
int size = intervals.size();
long totalTimeInterval = 0;
for (int i = 0; i < size; i++) {
System.out.println(intervalsArray[i]);
totalTimeInterval += intervalsArray[i];
}
int averageResponseTime = (int) (totalTimeInterval / size);

TestResult testResult = new TestResult();
testResult.setNinetyFivePercentile(ninetyFiveInterval);
testResult.setAverageResponseTime(averageResponseTime);
return testResult;
}


/*
inner class
*/

//线程执行体
private class RequestRunner implements Runnable {

private final CountDownLatch startLatch;
private final CountDownLatch endLatch;
private final Integer index;
private final ConcurrentMap<Integer, Long> concurrentMap;
private final String getRequestUrl;

public RequestRunner(CountDownLatch startLatch, CountDownLatch endLatch, int index, ConcurrentMap<Integer, Long> requestIntervalMap, String getRequestUrl) {
this.startLatch = startLatch;
this.endLatch = endLatch;
this.index = index;
this.concurrentMap = requestIntervalMap;
this.getRequestUrl = getRequestUrl;
}

@Override
public void run() {
//等待执行
try {
startLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}

//记录执行时间
long startTime = System.currentTimeMillis();
//执行请求
getRequest(getRequestUrl);
concurrentMap.put(index, System.currentTimeMillis() - startTime);
//执行完成
endLatch.countDown();
}

private void getRequest(String getRequestUrl) {
RestTemplate restTemplate = new RestTemplate();
try {
ResponseEntity<String> response = restTemplate.getForEntity(getRequestUrl, String.class);
System.out.println("请求" + this.index + ": 结果" + response.getStatusCode().value());
} catch (Exception e) {
System.out.println("请求" + this.index + ": 异常" + e.getMessage());
}
}
}
}



测试

public static void main(String[] args) {
WebRequestPressureTest test = new CommonWebRequestPressureTest();
TestResult testResult = test.testRequest("https://www.baidu.com", 10, 100);
System.out.println("平均响应时间(毫秒): " + testResult.getAverageResponseTime() + " 95%响应时间(毫秒) : " + testResult.getNinetyFivePercentile());
}

某次结果为:

平均响应时间(毫秒): 612 95%响应时间(毫秒) : 2340



问题:

1、若并发次数不是小数目,如万级和十万级,一次运行这么大量级的线程,CPU和内存会不会满了?有待验证。那百万级别并发如何模拟?

2、记录时间的优化思路。

一是使用异步事件来记录,这样能降低对线程执行的阻塞,当然目前只是记录long值,所以没必要花大力气实现异步方案。

二是是否有必要记录所有的调用时间开销才能计算平均响应时间和95%响应时间。个人觉得只需记录总时间(用于计算平均响应时间)和少量时间(用于计算95%响应时间)。以95%响应时间为例,在100次请求时,只需记录最大的6个时间开销即可。实际操作若记录开销不大,则不必要如此计较。

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

iHai

关注

还未添加个人签名 2018.07.26 加入

还未添加个人简介

评论

发布
暂无评论
第七周课后作业