架构师训练营 - 命题作业 第 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;@Datapublic 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;@Datapublic 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
版权声明: 本文为 InfoQ 作者【水边】的原创文章。
原文链接:【http://xie.infoq.cn/article/70ff9cfa15f23e7d84453e39e】。文章转载请联系作者。
水边
关注
还未添加个人签名 2019.04.14 加入
还未添加个人简介
评论