写点什么

架构师训练营:第七周作业

用户头像
zcj
关注
发布于: 2020 年 07 月 22 日

一 第一题

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




已知,吞吐量=(1000/响应时间)X 并发数量。如上图:随着并发用户数量增加,系统的响应时间,和吞储量变化曲线分四个阶段

  • 第一阶段:从开始 a 点到 b 点,此时随着并发数量的增加,系统的响应时间变化不大,吞吐量稳定上升,因为此阶段系统的资源利用率在上升,系统的资源能够同时处理响应的并发数量。

  • 第二阶段:从 b 点到 c 点,此时随着并发数量的增加,系统的响应时间明显变长,而吞吐量受响应时间和并发数量的综合影响,增长趋势逐渐变缓。因为此时,系统的资源利用率已经接近满载状态,开始出现资源争夺的情况,所有响应时间明显边长了。

  • 第三阶段:从 c 点到 d 点之间,此时随着并发数量的增加,系统的响应时间急剧变大,而吞吐量也呈现下降趋势。因为此时系统已经高负载处理请求,请求的数量远大于正常的负载量。请求处理的大部分时间在排队抢资源。所以响应时间急剧增加,吞吐量也有所下降。

  • 第四阶段:d 点以后,系统已经不可用,不能正常响应请求。原因,高并发下,系统的各种资源都承受了巨大的请求压力。在这种压力下出现问题概率很大,代码漏洞导致内存耗尽、cpu 满载。硬件温度过高等。

二 第二题

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


分析:

1、需要一个发送 http 请求的工具类

2、并发发送请求,需要使用多线程编程技术

3、每个请求需要记录请求响应的时间

4、每个发送 http 请求的响应时间需要汇总做统计


实现思路:

1、发送 http 请求直接使用 java 的 URL 请求

2、并发发送请求,需要使用多线程编程技术,使用线程池管理多线程。

3、需要使用锁工具类准确的控制并发请求的数量。

4、线程池中线程发送 http 请求记录请求时间,主线程汇总各个请求的时间做统计

5、用到闭锁工具类,使主线程知道所有请求都已经发送完毕后获取请求时间做统计


代码如下:

import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.net.HttpURLConnection;import java.net.MalformedURLException;import java.net.URL;import java.util.ArrayList;import java.util.Comparator;import java.util.List;import java.util.Scanner;import java.util.concurrent.*;import java.util.concurrent.atomic.AtomicLong;import java.util.stream.Collectors;
/** *用你熟悉的编程语言写一个 web 性能压测工具,输入参数:URL,请求总次数,并发数。 * 输出参数:平均响应时间,95% 响应时间。 * 用这个测试工具以 10 并发、100 次请求压测 www.baidu.com。 */public class HttpTestUtil2 { public static void main(String[] args) throws InterruptedException { String urlStr = "http://time.geekbang.org"; System.out.println("请输入参数请求url、请求总次数、并发请求数,例如:url=http://time.geekbang.org totalCount=100 threadCount=10"); Scanner scanner = new Scanner(System.in); String urlstr = null; int totalCount = 0; int threadCount = 0; try { urlstr = scanner.findInLine("url=\\S*").replace("url=",""); totalCount = Integer.parseInt(scanner.findInLine("totalCount=\\S*").replace("totalCount=","")); threadCount = Integer.parseInt(scanner.findInLine("threadCount=\\S*").replace("threadCount=","")); } catch (Exception e) { System.err.println("错误,请求正确输入参数!"); return ; } if (totalCount < 0 || threadCount < 0) { System.out.println("请输入正确数值"); return ; } URL url=null; try { url = new URL(urlstr); } catch (MalformedURLException e) { System.out.println("请输入正确url"); return ; }
test(totalCount,threadCount,url); } public static void test(int totalRequest, int concurrentThreads,URL url)throws InterruptedException { //线程池队列 ArrayBlockingQueue blockingQueue= new ArrayBlockingQueue<Runnable>(totalRequest); //线程池 固定线程数量为并发请求的数量 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(concurrentThreads, concurrentThreads, 0, TimeUnit.SECONDS, blockingQueue, (task)-> new Thread(task)); //所有请求任务的List List<HttpTask> tasks = new ArrayList<>(); //栅栏,控制每10个请求同时发送 CyclicBarrier cyclicBarrier = new CyclicBarrier(concurrentThreads); //闭锁,用于使主线程知道所有请求都已经发送过 CountDownLatch countDownLatchEnd = new CountDownLatch(totalRequest); for (int i = 0; i < totalRequest;i++) { HttpTask httpTask = new HttpTask(url,countDownLatchEnd,cyclicBarrier); threadPoolExecutor.submit(httpTask); tasks.add(httpTask); } //等待所有请求都发送完 countDownLatchEnd.await(); //关闭线程池 threadPoolExecutor.shutdown(); //汇总每个请求任务中 请求到响应接收花的时间 List<Long> responseTimes=tasks.stream().map(HttpTask::getResponseTime).collect(Collectors.toList()); // 结果统计 控制台输出 printResult(url, totalRequest, concurrentThreads, responseTimes);
}
public static void printResult(URL url,int totalRequest,int concurrentThreads,List<Long> responseTimeList) {
System.out.println("url:"+url+" 请求总数:"+totalRequest+" 并发数量:"+concurrentThreads); List<Long> successCount = responseTimeList.stream().filter(time -> time >= 0).collect(Collectors.toList()); successCount.sort(Comparator.comparingLong(time2 -> time2)); System.out.println(responseTimeList.size()+"次请求,每个请求时间如下:"+responseTimeList); AtomicLong sum = new AtomicLong(); successCount.forEach(time -> sum.addAndGet(time)); System.out.println("失败数量:"+(responseTimeList.size()-successCount.size())); if (successCount.size() > 0) { System.out.println("平均响应时间:" +sum.doubleValue()/successCount.size() +"毫秒 95%响应时间"+successCount.get((int) (successCount.size()*0.95))+"毫秒"); }
}
//线程池执行的子任务类 发送http请求并记录时间 public static class HttpTask implements Runnable{ //请求开始到响应的时间 构造初始化为-1默认为失败的请求 private Long responseTime; private final URL url; private final CyclicBarrier cyclicBarrier; private final CountDownLatch downLatchEnd; public HttpTask(URL url, CountDownLatch downLatchEnd,CyclicBarrier cyclicBarrier) { this.url = url; this.downLatchEnd = downLatchEnd; this.cyclicBarrier = cyclicBarrier; this.responseTime=-1L; }
public Long getResponseTime() { return responseTime; } @Override public void run() { try { getRequest(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); }finally { //子任务执行完毕 计数器减一 downLatchEnd.countDown(); } }
private void getRequest() throws BrokenBarrierException, InterruptedException { //等待一组线程同时到达此位置后同时发送请求 cyclicBarrier.await(); HttpURLConnection urlConnection = null; try { //开始请求的时间 long time=System.currentTimeMillis(); urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setDoInput(true); urlConnection.setInstanceFollowRedirects(true); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); String line = null; while ((line = bufferedReader.readLine()) != null) {// System.out.println(line); } //收到响应 记录时间 出现异常表示请求失败 时间为-1 responseTime=System.currentTimeMillis()-time; } catch (IOException e) { e.printStackTrace(); }finally { if(urlConnection != null) { urlConnection.disconnect(); } }
} }}
复制代码

压测http://www.baidu.com的结果

tips:百度对本人的网络不友好,丢包严重,100 请求 3 分钟内都出不来结果。所以试了一次 10 请求的。

请输入参数请求url、请求总次数、并发请求数,例如:url=http://www.baidu.com  totalCount=100 threadCount=10url=http://www.baidu.com  totalCount=10 threadCount=10url:http://www.baidu.com 请求总数:10 并发数量:1010次请求,每个请求时间如下:[1908, 1918, 1928, 1931, 1933, 1984, 1984, 2178, 2929, 2999]失败数量:0 平均响应时间:2169.2毫秒 95%响应时间2999毫秒
复制代码


压测http://time.geekbang.org的结果

请输入参数请求url、请求总次数、并发请求数,例如:url=http://time.geekbang.org  totalCount=100 threadCount=10url=http://time.geekbang.org  totalCount=100 threadCount=10url:http://time.geekbang.org 请求总数:100 并发数量:10100次请求,每个请求时间如下:[130, 153, 131, 145, 141, 149, 145, 130, 147, 146, 115, 117, 102, 118, 119, 120, 120, 147, 104, 102, 111, 109, 114, 116, 119, 121, 129, 128, 115, 121, 106, 60, 111, 122, 101, 121, 120, 103, 120, 107, 119, 100, 116, 117, 106, 120, 103, 100, 124, 54, 113, 111, 123, 123, 111, 112, 124, 128, 117, 97, 122, 106, 104, 103, 116, 120, 120, 103, 107, 120, 115, 119, 53, 110, 124, 113, 127, 111, 127, 62, 119, 119, 109, 112, 130, 108, 109, 125, 118, 131, 101, 115, 116, 115, 99, 100, 121, 122, 118, 102]失败数量:0平均响应时间:115.24毫秒 95%响应时间146毫秒
复制代码

计算一下吞吐量:

吞吐量=(1000/平均响应时间)*并发数量

带入数值后 极客邦的 10 并发请求吞吐量约为:86/sec

多次测试结果不一样,猜测可能是反向代理或者缓存的作用。


用户头像

zcj

关注

还未添加个人签名 2019.10.12 加入

精神小伙

评论

发布
暂无评论
架构师训练营:第七周作业