写点什么

架构师 0 期 | 手写一个性能压测工具(Java 版)

用户头像
刁架构
关注
发布于: 2020 年 07 月 22 日
架构师 0 期 | 手写一个性能压测工具(Java版)

问题一

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



系统响应时间变化

随着并发压力的增加,系统响应时间刚开始会较平缓的增加(即c点之前)。

过了c点(系统最大负载点)之后,响应时间急剧增加,很快就会达到d点(系统崩溃点)。

到了d点之后,系统崩溃,响应时间无限大,即系统失去响应。



系统吞吐量

随着并发压力的增加,系统的吞吐量刚开始会几乎直线增加到达b点(最佳性能点,一般系统都会选择稳定在b点左右)。

随后换变得相对缓慢增加,慢慢逼近c点(负载均衡点)。此时,系统吞吐量达到最大。

c点之后急剧下降,系统已超过系统最大承载量,性能开始下降,直到崩溃。



问题二

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



项目工程结构



创建压测工具类

ConcurrentTestUtil

package com.company;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 压测工具类
*/
public class ConcurrentTestUtil {
/**
* 压力测试方法
*
* @param url 需要压测的网址URL
* @param requestNum 压测的次数
* @param concurrentNum 并发次数
* @throws Exception
*/
public static void doRequest(String url,
int requestNum,
int concurrentNum) throws Exception {
//创建一个线程池
ExecutorService executor = Executors.newFixedThreadPool(concurrentNum);
//存放请求响应时间的列表
List<Long> timeList = new ArrayList<>();
//记录开始时间(方便计算总时长)
long startTime = System.currentTimeMillis();
//计时器
CountDownLatch latch = new CountDownLatch(requestNum);
//发起网络请求
HttpUtil.sendGet(url, null);
//循环发起请求
for (int i = 0; i < requestNum; i++) {
executor.execute(() -> {
long start = System.currentTimeMillis();
HttpUtil.sendGet(url, null);
latch.countDown();
timeList.add(System.currentTimeMillis() - start);
});
}
latch.await();
executor.shutdown();
//对数据进行排序
Collections.sort(timeList);
//计算请求总时长
long totalCostTime = System.currentTimeMillis() - startTime;
//打印
System.out.println("压测的网址: " + url);
System.out.println("压测总次数: " + requestNum);
System.out.println("并发线程数: " + concurrentNum);
System.out.println("总花费时间(毫秒): " + totalCostTime);
System.out.println("平均花费时间(毫秒): " + ((double) totalCostTime / requestNum));
System.out.println("95%时间(毫秒): " + timeList.get(95 * requestNum / 100));
System.out.println("---------------------------");
for (int i = 0; i < requestNum; i++) {
long time = timeList.get(i);
System.out.println("第" + i + "次: " + time + "毫秒");
}
System.out.println("---------------------------");
}
}



创建测试方法

package com.company;
public class Main {
public static void main(String[] args) {
try {
ConcurrentTestUtil.doRequest("https://www.baidu.com", 100, 10);
} catch (Exception e) {
e.printStackTrace();
}
}
}

网络请求工具类

package com.company;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;
public class HttpUtil {
/**
* 向指定URL发送GET方式的请求
* @param url 发送请求的URL
* @param param 请求参数
* @return URL 代表远程资源的响应
*/
public static String sendGet(String url, String param){
String result = "";
String urlName = url + "?" + param;
try{
URL realUrl = new URL(urlName);
//打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
//设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
//建立实际的连接
conn.connect();
//获取所有的响应头字段
Map<String,List<String>> map = conn.getHeaderFields();
//遍历所有的响应头字段
for (String key : map.keySet()) {
System.out.println(key + "-->" + map.get(key));
}
// 定义 BufferedReader输入流来读取URL的响应
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常" + e);
e.printStackTrace();
}
return result;
}
/**
* 向指定URL发送POST方式的请求
* @param url 发送请求的URL
* @param param 请求参数
* @return URL 代表远程资源的响应
*/
public static String sendPost(String url, String param){
String result = "";
try{
URL realUrl = new URL(url);
//打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
//设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
//发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
//获取URLConnection对象对应的输出流
PrintWriter out = new PrintWriter(conn.getOutputStream());
//发送请求参数
out.print(param);
//flush输出流的缓冲
out.flush();
// 定义 BufferedReader输入流来读取URL的响应
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
String line;
while ((line = in.readLine()) != null) {
result += "\n" + line;
}
} catch (Exception e) {
System.out.println("发送POST请求出现异常" + e);
e.printStackTrace();
}
return result;
}
//测试发送GET和POST请求
public static void main(String[] args) throws Exception{
//发送GET请求
String s = HttpUtil.sendGet("http://www.baidu.com",null);
System.out.println(s);
//发送POST请求
String s1 = HttpUtil.sendPost("http://www.baidu.com", "query=美丽人生");
System.out.println(s1);
}
}



压测执行结果

对请求的结果进行了排序,所以打出来的明细是从小到大的。

方便计算95%响应时间。

压测的网址: https://www.baidu.com
压测总次数: 100
并发线程数: 10
总花费时间(毫秒): 5084
平均花费时间(毫秒): 50.84
95%时间(毫秒): 731
---------------------------
第0次: 15毫秒
第1次: 16毫秒
第2次: 16毫秒
第3次: 16毫秒
第4次: 16毫秒
第5次: 18毫秒
第6次: 18毫秒
第7次: 18毫秒
第8次: 18毫秒
第9次: 18毫秒
第10次: 18毫秒
第11次: 19毫秒
第12次: 19毫秒
第13次: 19毫秒
第14次: 19毫秒
第15次: 19毫秒
第16次: 20毫秒
第17次: 20毫秒
第18次: 20毫秒
第19次: 22毫秒
第20次: 22毫秒
第21次: 23毫秒
第22次: 23毫秒
第23次: 23毫秒
第24次: 24毫秒
第25次: 24毫秒
第26次: 24毫秒
第27次: 24毫秒
第28次: 25毫秒
第29次: 25毫秒
第30次: 25毫秒
第31次: 25毫秒
第32次: 26毫秒
第33次: 26毫秒
第34次: 26毫秒
第35次: 27毫秒
第36次: 28毫秒
第37次: 28毫秒
第38次: 29毫秒
第39次: 29毫秒
第40次: 30毫秒
第41次: 30毫秒
第42次: 30毫秒
第43次: 30毫秒
第44次: 31毫秒
第45次: 31毫秒
第46次: 32毫秒
第47次: 32毫秒
第48次: 33毫秒
第49次: 35毫秒
第50次: 35毫秒
第51次: 36毫秒
第52次: 36毫秒
第53次: 38毫秒
第54次: 41毫秒
第55次: 45毫秒
第56次: 45毫秒
第57次: 46毫秒
第58次: 50毫秒
第59次: 52毫秒
第60次: 55毫秒
第61次: 55毫秒
第62次: 56毫秒
第63次: 56毫秒
第64次: 57毫秒
第65次: 65毫秒
第66次: 107毫秒
第67次: 109毫秒
第68次: 109毫秒
第69次: 110毫秒
第70次: 120毫秒
第71次: 146毫秒
第72次: 187毫秒
第73次: 197毫秒
第74次: 223毫秒
第75次: 231毫秒
第76次: 232毫秒
第77次: 233毫秒
第78次: 237毫秒
第79次: 239毫秒
第80次: 244毫秒
第81次: 253毫秒
第82次: 255毫秒
第83次: 258毫秒
第84次: 263毫秒
第85次: 280毫秒
第86次: 313毫秒
第87次: 319毫秒
第88次: 342毫秒
第89次: 451毫秒
第90次: 480毫秒
第91次: 519毫秒
第92次: 601毫秒
第93次: 680毫秒
第94次: 695毫秒
第95次: 731毫秒
第96次: 769毫秒
第97次: 898毫秒
第98次: 1138毫秒
第99次: 2699毫秒
---------------------------

Process finished with exit code 0



真机结果



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

刁架构

关注

叫我刁架构 2017.10.25 加入

预备备网红首席架构师,移动端开发者,边缘设计支持者。

评论

发布
暂无评论
架构师 0 期 | 手写一个性能压测工具(Java版)