架构师训练营 -week7 web 性能压测工具

用户头像
尔东雨田
关注
发布于: 2020 年 07 月 20 日

题目

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



实现

HttpClient请求类

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
public class HttpClient {
public static String doGet(String url,boolean isGzip) {
final HttpURLConnection httpURLConnection = getHttpURLConnection(url);
if(isGzip){
setGizp.accept(httpURLConnection);
}
setDefaultUserAgent.accept(httpURLConnection);
setMedthod.accept(httpURLConnection,"GET");
return getResponse(httpURLConnection,isGzip, HttpClient::byteStream2string);
}
public static HttpURLConnection getHttpURLConnection(String strUrl) {
try {
// 创建远程url连接对象
URL url = new URL(strUrl);
// 通过远程url连接对象打开一个连接,强转成httpURLConnection类
return (HttpURLConnection) url.openConnection();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public final static BiConsumer<HttpURLConnection, String> setMedthod = (httpURLConnection, method) -> {
try {
httpURLConnection.setRequestMethod(method);
} catch (ProtocolException e) {
e.printStackTrace();
}
};
public final static BiConsumer<HttpURLConnection, Integer> setTimeout = (httpURLConnection, timeout) -> {
httpURLConnection.setConnectTimeout(timeout * 1000);
};
public final static BiConsumer<HttpURLConnection, Map<String, String>> setCookie = (httpURLConnection, cookieMap) -> {
httpURLConnection.addRequestProperty("Cookie", cookieMap.keySet().stream()
.map(key -> key + "=" + cookieMap.get(key))
.collect(Collectors.joining(";"))
);
};
public final static BiConsumer<HttpURLConnection, String> setReferer = (httpURLConnection, s) -> {
httpURLConnection.addRequestProperty("referer", s);
};
public final static BiConsumer<HttpURLConnection, String> setUserAgent = (httpURLConnection, s) -> {
httpURLConnection.addRequestProperty("User-Agent",s);
};
public final static Consumer<HttpURLConnection> setDefaultUserAgent = httpURLConnection -> setUserAgent.accept(httpURLConnection,"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Mobile Safari/537.36");
public final static Consumer<HttpURLConnection> setGizp = httpURLConnection -> httpURLConnection.addRequestProperty("Accept-Encoding","gzip, deflate, br");
public static <T> T getResponse(HttpURLConnection httpURLConnection, Boolean enableGzip, Function<InputStream,T> callback) {
try{
InputStream inputStream = httpURLConnection.getInputStream();
boolean isGizp = enableGzip || "gzip".equals(httpURLConnection.getHeaderField("Content-Encoding"));
if (isGizp) {
inputStream = new GZIPInputStream(inputStream);
}
int responseCode = httpURLConnection.getResponseCode();
if(responseCode != 200){
throw new RuntimeException(responseCode + " 请求异常");
}
if (callback == null) {
inputStream.close();
} else {
return callback.apply(inputStream);
}
} catch (Exception e) {
return null;
}
return null;
}
public static String byteStream2string(InputStream inputStream) {
StringBuilder result = new StringBuilder();
try (InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
BufferedReader reader = new BufferedReader(inputStreamReader);
) {
String line = null;
while ((line = reader.readLine()) != null) {
result.append(line);
result.append("\r\n");
}
return result.toString();
} catch (IOException e) {
return null;
}
}
}



WebTest 测试类

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

public class WebTest {

public static void main(String[] args) throws InterruptedException {
if(args.length != 3 ){
throw new IllegalArgumentException("请输入参数:URL,请求总次数,并发数");
}

System.out.println("URL:"+args[0]);
System.out.println("请求总次数:"+args[1]);
System.out.println("并发数:"+args[2]);

String url = args[0];
int totalCount = Integer.valueOf(args[1]);
int concurrencyCount = Integer.valueOf(args[2]);

AtomicInteger totalRows = new AtomicInteger(0);
AtomicInteger failRows = new AtomicInteger(0);
// 创建线程池,其中核心线程10,也是最大并发数,最大线程数和队列大小都为100,即总任务数
ThreadPoolExecutor executor = new ThreadPoolExecutor(concurrencyCount, totalCount, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(totalCount));
List<Long> respList = new ArrayList<>(totalCount);
// 初始化CountDownLatch,大小为totalCount
CountDownLatch countDownLatch = new CountDownLatch(totalCount);
// 模拟遍历参数集合
for (int i = 0; i < totalCount; i++) {
// 往线程池提交任务
executor.execute(new Runnable() {
@Override
public void run() {
long startTime = System.currentTimeMillis();
String result = HttpClient.doGet(url, true);
long costTime = System.currentTimeMillis()- startTime;
respList.add(costTime);

if(result == null){
failRows.incrementAndGet();
}
totalRows.incrementAndGet();
// 子线程完成,countDownLatch执行countDown
countDownLatch.countDown();
}
});
// 打印线程池运行状态
// System.out.println("线程池中线程数目:" + executor.getPoolSize() + ",队列中等待执行的任务数目:" +
// executor.getQueue().size() + ",已执行结束的任务数目:" + executor.getCompletedTaskCount());
}
// 标记多线程关闭,但不会立马关闭
executor.shutdown();

// 阻塞当前线程,直到所有子线程都执行countDown方法才会继续执行
countDownLatch.await();

// 打印线程池运行状态
// System.out.println("线程池中线程数目:" + executor.getPoolSize() + ",队列中等待执行的任务数目:" +
// executor.getQueue().size() + ",已执行结束的任务数目:" + executor.getCompletedTaskCount());
// 总用时
long sumTime = Optional.ofNullable(respList).orElse(new ArrayList<>()).stream()
.mapToLong(Long::longValue).sum();


// 平均响应时间
long avgTime = Optional.ofNullable(respList).orElse(new ArrayList<>()).stream()
.map(Long::longValue)
.collect(Collectors.averagingLong(Long::longValue)).longValue();

// 95%响应时间
long tt95AvgTime = Optional.ofNullable(respList).get()
.stream()
.sorted()
.collect(Collectors.toList())
.get(Math.max(Math.floorDiv(respList.size()*95,100)-1,0));

// 计数
System.out.println("压测结束");
System.out.println("总用时ms:" + sumTime);
System.out.println("总请求数:" + totalRows.get());
System.out.println("失败数:" + failRows.get());
System.out.println("成功数:" + (totalRows.get() - failRows.get()));
System.out.println("平均响应时间ms:"+avgTime);
System.out.println("95%响应时间ms:"+tt95AvgTime);


}

}



测试结果

/Users/chenlei/private/designpattern/out/production/designpattern com.chenlei.demo.webtest.WebTest https://www.baidu.com 100 10
URL:https://www.baidu.com
请求总次数:100
并发数:10
压测结束
总用时ms:36536
总请求数:100
失败数:0
成功数:100
平均响应时间ms:365
95%响应时间ms:1215
Process finished with exit code 0



用户头像

尔东雨田

关注

预备用枪! 2017.12.12 加入

还未添加个人简介

评论

发布
暂无评论
架构师训练营 -week7 web 性能压测工具