架构师训练营 - 命题作业 第 7 周

用户头像
水边
关注
发布于: 2020 年 07 月 20 日
  • 用你熟悉的编程语言写一个 web 性能压测工具,输入参数:URL,请求总次数,并发数。输出参数:平均响应时间,95% 响应时间。用这个测试工具以 10 并发、100 次请求压测 www.baidu.com。

答:

作业完整代码已经提交到Github上,地址:

https://github.com/youbl/study/tree/master/paralleltest



下面给出主要代码部分(不含http请求工具类代码):

1、先定义输入类:

package cn.beinet.dto;

import lombok.Data;

@Data
public class TestInputDto {
/**
* 测试地址
*/
private String url;
/**
* 总请求数
*/
private long requestTime;
/**
* 总并发数
*/
private long concurrencyNum;

/**
* 每个线程要请求的次数.
* 不考虑除不断的情况
* @return 次数
*/
public long getTimePerThread() {
return requestTime / concurrencyNum;
}

@Override
public String toString() {
return String.format("压测地址: %s 压测次数: %s 并发数: %s", url, requestTime, concurrencyNum);
}
}



2、定义输出类:

package cn.beinet.dto;

import lombok.Data;
import lombok.Synchronized;

import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

@Data
public class TestOutputDto {
/**
* 收集所有的请求时长
*/
private SortedMap<Long, Long> map = new TreeMap<>();

@Synchronized
public void putTime(long time) {
map.put(time, 0L);
}

@Override
public String toString() {
long totalTime = 0; // 总的请求时长
long time95 = 0; // 95分位请求时长
long begin95 = map.size() * 95 / 100; // 95分位请求所在的索引位置
long idx = 0;
for (Map.Entry<Long, Long> item : map.entrySet()) {
if (idx == begin95)
time95 = item.getKey();
totalTime += item.getKey();
idx++;
}
long avg = totalTime / map.size();

return String.format("压测结果:平均响应时间: %s纳秒 95分位响应时间: %s纳秒", avg, time95);
}
}



3、定义压测类:

package cn.beinet;

import cn.beinet.dto.TestInputDto;
import cn.beinet.dto.TestOutputDto;
import cn.beinet.utils.HttpHelper;
import lombok.Synchronized;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ForTestService implements Runnable {
private TestInputDto inputDto;
private int threadNum;
TestOutputDto ret = new TestOutputDto();

public ForTestService(TestInputDto inputDto) {
this.inputDto = inputDto;
}

public TestOutputDto startTest() throws InterruptedException {
// 抛弃第一次请求,避免DNS解析等损耗
sendRequest();

ExecutorService pool = Executors.newCachedThreadPool();

// 并行启动ConsurrencyNum个线程
for (int i = 0; i < inputDto.getConcurrencyNum(); i++) {
pool.execute(this);
}

// 等待任务完成
do {
Thread.sleep(10000);
System.out.println(threadNum);
} while (threadNum > 0);

pool.shutdown();
return ret;
}

@Override
public void run() {
long perTime = inputDto.getTimePerThread();
addThread();
try {
// 每个线程顺序执行请求
for (int requestIdx = 0; requestIdx < perTime; requestIdx++) {
long costTime = sendRequest();
ret.putTime(costTime);
}
} catch (Exception exp) {
System.out.println("有线程出错了:" + exp.getMessage());
} finally {
decThread();
}

}

@Synchronized
private void addThread() {
threadNum++;
}

@Synchronized
private void decThread() {
threadNum--;
}

/**
* 发请求
* @return 返回请求耗时,纳秒
*/
private long sendRequest() {
long beginTime = System.nanoTime();
HttpHelper.GetPage(inputDto.getUrl(), "");
return System.nanoTime() - beginTime;
}
}



4、main主调方法:

package cn.beinet;

import cn.beinet.dto.TestInputDto;
import cn.beinet.dto.TestOutputDto;
import org.apache.commons.lang3.StringUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Hello world!
*/
public class App {
public static void main(String[] args) throws IOException, InterruptedException {
TestInputDto dto = getInputDto();
System.out.println(dto + " 开始测试");

TestOutputDto result = new ForTestService(dto).startTest();
System.out.println("测试完成: " + result);
}

static TestInputDto getInputDto() throws IOException {
TestInputDto ret = new TestInputDto();
System.out.print("请输入测试地址,默认值 https://www.baidu.com/: ");
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
ret.setUrl(reader.readLine());
if (StringUtils.isEmpty(ret.getUrl())) {
ret.setUrl("https://www.baidu.com/");
} else {
ret.setUrl(ret.getUrl().trim());
Pattern reg = Pattern.compile("^(?i)https?://");
Matcher m1 = reg.matcher(ret.getUrl());
if (!m1.find()) {
ret.setUrl("http://" + ret.getUrl());
}
}

String strRequestTime;
do {
System.out.print("请输入总请求次数,默认值 100: ");
strRequestTime = reader.readLine();
if (StringUtils.isEmpty(strRequestTime))
strRequestTime = "100";
} while (!StringUtils.isNumeric(strRequestTime));
ret.setRequestTime(Long.parseLong(strRequestTime));

String strConcurrencyNum;
do {
System.out.print("请输入并发数,默认值 10: ");
strConcurrencyNum = reader.readLine();
if (StringUtils.isEmpty(strConcurrencyNum))
strConcurrencyNum = "10";
} while (!StringUtils.isNumeric(strConcurrencyNum));
ret.setConcurrencyNum(Long.parseLong(strConcurrencyNum));

reader.close();

return ret;
}
}



5、最终输出结果:

请输入测试地址,默认值 https://www.baidu.com/:
请输入总请求次数,默认值 100:
请输入并发数,默认值 10:
压测地址: https://www.baidu.com/ 压测次数: 100 并发数: 10 开始测试
0
测试完成: 压测结果:平均响应时间: 36772742纳秒 95分位响应时间: 119367700纳秒



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

水边

关注

还未添加个人签名 2019.04.14 加入

还未添加个人简介

评论

发布
暂无评论
架构师训练营 - 命题作业 第 7 周