性能压测与实践
Date: 2020/7/19 V1.0
Author: Jessie
性能测试指标
不同视角下有不同的性能标准,不同的标准由不同的性能测试指标。网站性能测试的主要指标由响应时间、并发数、吞吐量、性能计数器。
响应时间:应用系统从发出请求开始到收到最后响应数据所需要的时间。反映了系统的“快、慢”。
并发数:系统能同时处理的请求的数量。反映了系统的负载特性。对于网站而言,并发数指同时提交请求的用户数目,于此相对应,还有在线用户数(当前登录系统的用户数)和系统用户数(可能访问的系统总用户数)。
吞吐量:指单位时间内系统处理的请求的数量,体现系统的处理能力。
TPS:每秒事务数;
QPS:每秒查询数。
吞吐量= (1000/响应时间ms)*并发数
性能测试分三个阶段:性能测试、负载测试和压力测试。
性能测试:以设计初期的目标进行测试,给系统不断施压,验证系统在资源可接受范围内,是否达到性能预期。
负载测试:对系统不断增加并发请求以增加系统压力,知道系统的某项或多项性能到达安全临界值。这时候继续施压,系统处理能力反而下降。
压力测试:超过安全负载情况下,对系统继续施加压力,直到系统崩溃,以获得系统最大压力承受能力。
性能压测的时候,随着并发压力的增加,系统响应时间和吞吐量如何变化,为什么?
吞吐量= (1000/响应时间ms)*并发数
随着并发增加,系统性能在设计范围内,响应时间正常返回,TPS吞吐量在不断增长,达到上图b点,进入负载继续压测,系统响应时间变长,TPS吞吐量增长速度下降,看到曲线斜率下降。直达c点,到达系统性能临界值,系统积压的请求无法正常响应,并发不断增加,而响应阻塞,吞吐量迅速下降,直到系统崩溃。如上图所示的曲线变化图。
举个实际的例子,看出并发压力增加下,响应时间和吞吐量的变化。
性能测试实践
用你熟悉的编程语言写一个 web 性能压测工具,输入参数:URL,请求总次数,并发数。输出参数:平均响应时间,95% (响应排序后第95个请求的)响应时间。用这个测试工具以 10 并发、100 次请求压测 www.baidu.com。
代码:
HttpURL 类:处理HTTP的请求工具类:
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStreamWriter;import java.net.HttpURLConnection;import java.net.MalformedURLException;import java.net.URL;public class HttpURL { public static boolean doPost(String URL){ boolean result= true; OutputStreamWriter out = null; BufferedReader in = null; //StringBuilder result = new StringBuilder(); HttpURLConnection conn = null; try{ URL url = new URL(URL); conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); //发送POST请求必须设置为true conn.setDoOutput(true); conn.setDoInput(true); //设置连接超时时间和读取超时时间 conn.setConnectTimeout(30000); conn.setReadTimeout(10000); conn.setRequestProperty("Content-Type", "application/json"); conn.setRequestProperty("Accept", "application/json"); //获取输出流 out = new OutputStreamWriter(conn.getOutputStream()); String jsonStr = "{\"qry_by\":\"name\", \"name\":\"Tim\"}"; out.write(jsonStr); out.flush(); out.close(); //取得输入流,并使用Reader读取 if (200 == conn.getResponseCode()){ result = true; }else{ result = false; } }catch (Exception e){ result = false; }finally { try{ if(out != null){ out.close(); } if(in != null){ in.close(); } }catch (IOException ioe){ ioe.printStackTrace(); } } return result; } public static boolean doGet(String URL){ boolean result = true; HttpURLConnection conn = null; InputStream is = null; BufferedReader br = null; // StringBuilder result = new StringBuilder(); try{ //创建远程url连接对象 URL url = new URL(URL); //通过远程url连接对象打开一个连接,强转成HTTPURLConnection类 conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); //设置连接超时时间和读取超时时间 conn.setConnectTimeout(15000); conn.setReadTimeout(60000); conn.setRequestProperty("Accept", "application/json"); //发送请求 conn.connect(); //通过conn取得输入流,并使用Reader读取 if (200 == conn.getResponseCode()){ result = true; }else{ result = false; } }catch (MalformedURLException e){ result = false; e.printStackTrace(); }catch (IOException e){ result =false; e.printStackTrace(); }catch (Exception e){ result = false; e.printStackTrace(); }finally { try{ if(br != null){ br.close(); } if(is != null){ is.close(); } }catch (IOException ioe){ ioe.printStackTrace(); } conn.disconnect(); } return result; }}
WebTask:压测请求任务类
public class WebTask { private int id; private String reqURL; private boolean isSucess; private long time; private String reqType; public String getReqType() { return reqType; } public void setReqType(String reqType) { this.reqType = reqType; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getReqURL() { return reqURL; } public void setReqURL(String reqURL) { this.reqURL = reqURL; } public boolean isSucess() { return isSucess; } public void setSucess(boolean isSucess) { this.isSucess = isSucess; } public long getTime() { return time; } public void setTime(long time) { this.time = time; }}
WebTestThread:并发HTTP请求的线程类
import java.util.Date;public class WebTestThread extends Thread{ WebTask task; public WebTestThread(WebTask webTask){ this.task=webTask; } public void run() { Date now = new Date(); boolean isSuccess = true; if (task.getReqType().equals("post")) { isSuccess = HttpURL.doPost(task.getReqURL()); } else { isSuccess = HttpURL.doGet(task.getReqURL()); } Date doneTime = new Date(); long time = (doneTime.getTime() - now.getTime()); task.setSucess(isSuccess); task.setTime(time); System.out.println(task.getId() + "," + isSuccess + "," + time); }}
4.WebTest 类,构造10个并发发送100个Web请求。
import java.util.HashMap;import java.util.LinkedList;import java.util.Queue;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.ArrayList;public class WebTest { ArrayList sortList = new ArrayList(); public WebTest(int threadCount,LinkedList<WebTask> taskList) { //利用定长线程池控制并发数量,直到所有请求任务执行完 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(threadCount); for(WebTask task:taskList) { WebTestThread thread = new WebTestThread(task); fixedThreadPool.submit(thread); thread.start(); } fixedThreadPool.shutdown(); while(true) { if(fixedThreadPool.isTerminated()) { long sumTime = 0; long avgTime = 0; long failNum = 0; //计算任务执行结果的每个任务的耗时平均值\失败计数 for (int i = 0; i < taskList.size(); i++) { WebTask task = (WebTask)taskList.get(i); long time = task.getTime(); sumTime += time; if(!task.isSucess()) { failNum++; } sortList.add(time); } avgTime = sumTime / taskList.size(); // 排序 95%请求 sortList.sort(null); System.out.println("执行"+threadCount+" 次并发, "+taskList.size()+" 次请求结果如下:"); System.out.println("平均响应时间:" + avgTime); if(sortList.size()!=0) { int index = (int) Math.floor(taskList.size()*0.95); WebTask task = taskList.get(index); System.out.println("95%响应时间:" + task.getTime()); } System.out.println("失败请求数:" + failNum); break; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { int connNum = 10; int reqNum = 100; String url = "http://www.baidu.com"; String reqType ="post"; try { if(args[0]!=null && Integer.parseInt(args[0])!=0) { connNum = Integer.parseInt(args[0]); } if(args[1]!=null && Integer.parseInt(args[1])!=0) { reqNum = Integer.parseInt(args[1]); } if(args[2]!=null ) { url = args[2]; } }catch(Exception ex) { } LinkedList<WebTask> taskList = new LinkedList<WebTask>(); for(int i=0;i<reqNum;i++) { WebTask task = new WebTask(); task.setId(i); task.setReqURL(url); task.setReqType(reqType); taskList.add(task); } new WebTest(connNum,taskList); }}
压测结论:
执行10 次并发, 100 次请求结果如下:
平均响应时间:263ms
95%响应时间:297ms
失败请求数:0
系统执行的日志:
52,true,33215,true,33846,true,3331,true,34090,true,3287,true,33925,true,3400,true,3436,true,34272,true,33337,true,33839,true,33713,true,3448,true,3454,true,34710,true,35155,true,34422,true,35091,true,3419,true,35598,true,34414,true,35449,true,34895,true,37612,true,38618,true,38635,true,38776,true,38259,true,3855,true,39493,true,38243,true,3875,true,39599,true,38269,true,3863,true,3956,true,39468,true,38951,true,39044,true,39227,true,39620,true,39926,true,39966,true,3937,true,40131,true,39841,true,41512,true,3313,true,3814,true,2815,true,2816,true,2217,true,48617,true,2918,true,3119,true,2660,true,52796,true,5262,true,53820,true,3038,true,53262,true,53347,true,53492,true,53048,true,53416,true,5402,true,54258,true,53428,true,53934,true,53894,true,53165,true,53470,true,53311,true,54697,true,53542,true,54324,true,54781,true,54089,true,53967,true,5428,true,5503,true,55188,true,5404,true,55264,true,54329,true,54856,true,54463,true,54321,true,4786,true,58554,true,58878,true,58875,true,58840,true,59150,true,59079,true,59932,true,6059,true,60857,true,60130,true,60523,true,60773,true,60033,true,60545,true,60326,true,13725,true,13811,true,34327,true,16830,true,6328,true,14431,true,2932,true,3524,true,35422,true,37823,true,37421,true,91837,true,2538,true,2339,true,3340,true,2941,true,2642,true,3743,true,6044,true,5234,true,26633,true,26729,true,34745,true,5549,true,3346,true,3447,true,3548,true,3550,true,3151,true,2653,true,14252,true,14954,true,14855,true,14884,true,121482,true,121474,true,121657,true,1581,true,122780,true,121787,true,121683,true,121761,true,12220,true,123177,true,122236,true,122753,true,122519,true,123058,true,3985,true,122359,true,5760,true,5661,true,5763,true,4464,true,4167,true,3565,true,3666,true,3668,true,3469,true,3970,true,3371,true,3672,true,3974,true,3273,true,4476,true,3075,true,3877,true,5879,true,4378,true,4780,true,4881,true,4482,true,3083,true,3084,true,3471,true,156662,true,36190,true,2891,true,3586,true,24485,true,24710,true,132693,true,3192,true,4994,true,4396,true,4698,true,3397,true,3399,true,3235,true,102936,true,102395,true,29756,true,102188,true,103789,true,103387,true,1039执行10 次并发, 100 次请求结果如下:平均响应时间:26395%响应时间:297失败请求数:0
版权声明: 本文为 InfoQ 作者【架构5班杨娟Jessie】的原创文章。
原文链接:【http://xie.infoq.cn/article/86295e94f731ab91e71e62e9a】。文章转载请联系作者。
架构5班杨娟Jessie
还未添加个人签名 2018.08.21 加入
码过代码、做过产品;擅长码字、演讲、认真做事之人。
评论