架构师训练营:第七周作业
一 第一题
性能压测的时候,随着并发压力的增加,系统响应时间和吞吐量如何变化,为什么?


已知,吞吐量=(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=10
url=http://www.baidu.com totalCount=10 threadCount=10
url:http://www.baidu.com 请求总数:10 并发数量:10
10次请求,每个请求时间如下:[1908, 1918, 1928, 1931, 1933, 1984, 1984, 2178, 2929, 2999]
失败数量:0 平均响应时间:2169.2毫秒 95%响应时间2999毫秒
请输入参数请求url、请求总次数、并发请求数,例如:url=http://time.geekbang.org totalCount=100 threadCount=10
url=http://time.geekbang.org totalCount=100 threadCount=10
url:http://time.geekbang.org 请求总数:100 并发数量:10
100次请求,每个请求时间如下:[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 加入
精神小伙
评论