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

问题一
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.8495%时间(毫秒): 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
版权声明: 本文为 InfoQ 作者【刁架构】的原创文章。
原文链接:【http://xie.infoq.cn/article/5e6be2938a22fac37f6e00584】。文章转载请联系作者。

刁架构
关注
叫我刁架构 2017.10.25 加入
预备备网红首席架构师,移动端开发者,边缘设计支持者。
评论