性能压测与实践

发布于: 2020 年 07 月 19 日



Date:  2020/7/19    V1.0

      Author: Jessie



性能测试指标

不同视角下有不同的性能标准,不同的标准由不同的性能测试指标。网站性能测试的主要指标由响应时间、并发数、吞吐量、性能计数器。

响应时间:应用系统从发出请求开始到收到最后响应数据所需要的时间。反映了系统的“快、慢”。

并发数:系统能同时处理的请求的数量。反映了系统的负载特性。对于网站而言,并发数指同时提交请求的用户数目,于此相对应,还有在线用户数(当前登录系统的用户数)和系统用户数(可能访问的系统总用户数)。

吞吐量:指单位时间内系统处理的请求的数量,体现系统的处理能力。

  • TPS:每秒事务数;

  • QPS:每秒查询数。

  • 吞吐量= (1000/响应时间ms)*并发数

  •  

性能测试分三个阶段:性能测试、负载测试和压力测试。

  1. 性能测试:以设计初期的目标进行测试,给系统不断施压,验证系统在资源可接受范围内,是否达到性能预期。

  2. 负载测试:对系统不断增加并发请求以增加系统压力,知道系统的某项或多项性能到达安全临界值。这时候继续施压,系统处理能力反而下降。

  3. 压力测试:超过安全负载情况下,对系统继续施加压力,直到系统崩溃,以获得系统最大压力承受能力。



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



吞吐量= (1000/响应时间ms)*并发数

随着并发增加,系统性能在设计范围内,响应时间正常返回,TPS吞吐量在不断增长,达到上图b点,进入负载继续压测,系统响应时间变长,TPS吞吐量增长速度下降,看到曲线斜率下降。直达c点,到达系统性能临界值,系统积压的请求无法正常响应,并发不断增加,而响应阻塞,吞吐量迅速下降,直到系统崩溃。如上图所示的曲线变化图。

举个实际的例子,看出并发压力增加下,响应时间和吞吐量的变化。



性能测试实践

 

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

 

代码:

  1. 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;
}

}





  1. 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;
}


}

  1. 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);
}
}


  1. 压测结论:

执行10 次并发, 100 次请求结果如下:

平均响应时间:263ms

95%响应时间:297ms

失败请求数:0



  1. 系统执行的日志:

52,true,332
15,true,338
46,true,333
1,true,340
90,true,328
7,true,339
25,true,340
0,true,343
6,true,342
72,true,333
37,true,338
39,true,337
13,true,344
8,true,345
4,true,347
10,true,351
55,true,344
22,true,350
91,true,341
9,true,355
98,true,344
14,true,354
49,true,348
95,true,376
12,true,386
18,true,386
35,true,387
76,true,382
59,true,385
5,true,394
93,true,382
43,true,387
5,true,395
99,true,382
69,true,386
3,true,395
6,true,394
68,true,389
51,true,390
44,true,392
27,true,396
20,true,399
26,true,399
66,true,393
7,true,401
31,true,398
41,true,415
12,true,33
13,true,38
14,true,28
15,true,28
16,true,22
17,true,486
17,true,29
18,true,31
19,true,26
60,true,527
96,true,526
2,true,538
20,true,30
38,true,532
62,true,533
47,true,534
92,true,530
48,true,534
16,true,540
2,true,542
58,true,534
28,true,539
34,true,538
94,true,531
65,true,534
70,true,533
11,true,546
97,true,535
42,true,543
24,true,547
81,true,540
89,true,539
67,true,542
8,true,550
3,true,551
88,true,540
4,true,552
64,true,543
29,true,548
56,true,544
63,true,543
21,true,47
86,true,585
54,true,588
78,true,588
75,true,588
40,true,591
50,true,590
79,true,599
32,true,605
9,true,608
57,true,601
30,true,605
23,true,607
73,true,600
33,true,605
45,true,603
26,true,137
25,true,138
11,true,343
27,true,168
30,true,63
28,true,144
31,true,29
32,true,35
24,true,354
22,true,378
23,true,374
21,true,918
37,true,25
38,true,23
39,true,33
40,true,29
41,true,26
42,true,37
43,true,60
44,true,52
34,true,266
33,true,267
29,true,347
45,true,55
49,true,33
46,true,34
47,true,35
48,true,35
50,true,31
51,true,26
53,true,142
52,true,149
54,true,148
55,true,148
84,true,1214
82,true,1214
74,true,1216
57,true,158
1,true,1227
80,true,1217
87,true,1216
83,true,1217
61,true,1222
0,true,1231
77,true,1222
36,true,1227
53,true,1225
19,true,1230
58,true,39
85,true,1223
59,true,57
60,true,56
61,true,57
63,true,44
64,true,41
67,true,35
65,true,36
66,true,36
68,true,34
69,true,39
70,true,33
71,true,36
72,true,39
74,true,32
73,true,44
76,true,30
75,true,38
77,true,58
79,true,43
78,true,47
80,true,48
81,true,44
82,true,30
83,true,30
84,true,34
71,true,1566
62,true,361
90,true,28
91,true,35
86,true,244
85,true,247
10,true,1326
93,true,31
92,true,49
94,true,43
96,true,46
98,true,33
97,true,33
99,true,32
35,true,1029
36,true,1023
95,true,297
56,true,1021
88,true,1037
89,true,1033
87,true,1039
执行10 次并发, 100 次请求结果如下:
平均响应时间:263
95%响应时间:297
失败请求数:0




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

还未添加个人签名 2018.08.21 加入

码过代码、做过产品;擅长码字、演讲、认真做事之人。

评论

发布
暂无评论
性能压测与实践