性能压测

用户头像
拈香(曾德政)
关注
发布于: 2020 年 07 月 22 日
性能压测

性能压测

QPS和TPS

QPS

Queries Per Second意思是“每秒查询率”,是一台服务器每秒能够相应的查询次数,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。

TPS

TransactionsPerSecond是事务数/秒。一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。客户机在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数。



TPS事物

Tps包括三个过程,分别是:

  • 用户请求服务器

  • 服务器自己的内部处理

  • 服务器返回给用户



QPS和TPS区别

一次TPS表示一个事物的完整过程,这个过程中可能包含多个QPS。例如:访问一次页面是一次TPS,访问页面的时候页面有多个请求,每个请求算一次QPS。



系统吞吐量

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

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

系统吞吐量几个重要參数:QPS(TPS)、并发数、响应时间

  • QPS(TPS):每秒钟request/事务 数量

  • 并发数: 系统同一时候处理的request/事务数

  • 响应时间: 一般取平均响应时间



压力测试曲线图



伴随着并发数的增加系统响应时长和系统吞吐量表现出不一样的变化趋势。

  • 绿线代表CPU使用情况

  • 红线表示系统吞吐量

  • 紫线表示系统响应时间



系统响应时长变化

随着并发数的增加,系统响应时间的变化可以分为三个阶段。

  • 第一阶段

低负载阶段,系统资源利用率很低,系统响应时间随着并发数增加变化不明显,也可以理解为并发数增加并未对系统响应时长造成太大影响。

  • 第二阶段

高负载阶段,系统利用率较高,系统响应时长随着并发数增加出现大幅增长,在此阶段并发数对系统响应时长的影响很大,其主要原因是因为系统资源满载了,请求数量大于CPU的核心数,导致进程或者线程不断切换,响应耗时增大。

  • 第二阶段

过载阶段,系统利用率接近最大,系统过载。由于请求数量远大于CPU核心数量,系统为了处理如此大量的请求,进程(线程)频繁切换,导致系统响应时长成指数增长



系统吞吐量变化

同系统响应时长,系统吞吐量变化也可以分为三个阶段

  • 第一阶段

低负载阶段,系统利用率低,CPU存在空闲的情况,此时随着并发数的增加系统吞吐量正比例增加。

  • 第二阶段

高负载阶段,系统利用率达到瓶颈,CPU核心满负荷工作,此时出现进程或者线程不断切换的情况,导致随着并发数的增加系统吞吐量缓慢增长甚至出现小幅度下降的情况

  • 第二阶段

过载阶段,系统利用率过高,过多的请求导致CPU疲于应付,进程(线程)频繁切换,真正用于处理请求的时间变少,能够处理的请求数反而变少,系统的吞吐量出现明显的下降。



实现简单web压测工具

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



首先实现一个任务类 Tasker



public class Tasker implements Runnable {

private String url;
private CyclicBarrier threadStartBarrier;
private CountDownLatch threadEndLatch;
private int count;
private List<Long> everyTimes;

@Override
public void run() {
try {
this.threadStartBarrier.await();
for(int i = 0; i < this.count; ++i) {
long start = System.currentTimeMillis();
HttpURLConnection urlConnection = null;
try {
final URL link = new URL(url);
urlConnection = (HttpURLConnection) link.openConnection();
urlConnection.setRequestMethod("GET");
InputStream response = urlConnection.getInputStream();
response.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
long end = System.currentTimeMillis();
long limit = end - start;
System.out.println("线程【" + Thread.currentThread().getName()+ "】本次请求耗时【" + limit + "】");
this.everyTimes.add(limit);
}
}

this.threadEndLatch.countDown();
}catch (Exception e){
e.printStackTrace();
}
}

/**
* 构造函数
* @param url
* @param threadStartBarrier
* @param threadEndLatch
* @param count
*/
public Tasker(String url, CyclicBarrier threadStartBarrier, CountDownLatch threadEndLatch, int count) {
this.url = url;
this.threadStartBarrier = threadStartBarrier;
this.threadEndLatch = threadEndLatch;
this.count = count;
this.everyTimes = new ArrayList<>(count);
}


/**
* 获取本次任务所有请求的响应时间集合
* @return
*/
public List<Long> getEveryTimes() {
return this.everyTimes;
}
}



然后准备一个压测结果类StressResult



public class StressResult {
private int concurrencyNum;
private int totalRequest;
private double avgTime;
private long responseTimeOfPercent95;

public int getConcurrencyNum() {
return concurrencyNum;
}

public void setConcurrencyNum(int concurrencyNum) {
this.concurrencyNum = concurrencyNum;
}

public int getTotalRequest() {
return totalRequest;
}

public void setTotalRequest(int totalRequest) {
this.totalRequest = totalRequest;
}

public double getAvgTime() {
return avgTime;
}

public void setAvgTime(double avgTime) {
this.avgTime = avgTime;
}

public long getResponseTimeOfPercent95() {
return responseTimeOfPercent95;
}

public void setResponseTimeOfPercent95(long responseTimeOfPercent95) {
this.responseTimeOfPercent95 = responseTimeOfPercent95;
}

@Override
public String toString() {
return "StressResult{" +
"并发数 = " + concurrencyNum +
", 总请求数 = " + totalRequest +
", 平均响应时长 = " + avgTime +
", 95%响应时长 = " + responseTimeOfPercent95 +
'}';
}



最后实现压测工具类StressTasker



public class StressTasker {

//自定义线程名
private ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("stress-thread-%d").build();

/**
* 压测任务
* @param concurrencyLevel
* @param totalRequests
* @param url
* @return
*/
public StressResult doWork(int concurrencyLevel, int totalRequests, String url) {
//计算每个线程请求多少次
int everyThreadCount = totalRequests / concurrencyLevel;
//设置辅助类线程,用于结果统计,栅栏等待所有线程运行完成
CyclicBarrier threadStartBarrier = new CyclicBarrier(concurrencyLevel);
// 计数使用,所有子线程任务执行完才视为完全结束,保证所有都完成
CountDownLatch threadEndLatch = new CountDownLatch(concurrencyLevel);
//创建线程池
ExecutorService executorService = new ThreadPoolExecutor(concurrencyLevel, concurrencyLevel * 2, 0L,
TimeUnit.MILLISECONDS, new SynchronousQueue<>(),
namedThreadFactory, new ThreadPoolExecutor.CallerRunsPolicy());
List<Tasker> taskers = new ArrayList<>(concurrencyLevel);

int realTotalRequests;
Tasker tasker;
// 添加工作任务
for (realTotalRequests = 0; realTotalRequests < concurrencyLevel; ++realTotalRequests) {
tasker = new Tasker(url, threadStartBarrier, threadEndLatch, everyThreadCount);
taskers.add(tasker);
}
// 提交任务
for (realTotalRequests = 0; realTotalRequests < concurrencyLevel; ++realTotalRequests) {
tasker = taskers.get(realTotalRequests);
executorService.submit(tasker);
}

try {
threadEndLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 关闭线程
executorService.shutdownNow();
realTotalRequests = everyThreadCount * concurrencyLevel;
System.out.println("真实处理线程数量 = " + realTotalRequests);

// 执行结果
StressResult stressResult = this.getStressResult(taskers);
stressResult.setConcurrencyNum(concurrencyLevel);
stressResult.setTotalRequest(totalRequests);
return stressResult;
}


/**
* 压测结果处理
* @param taskers
* @return
*/
protected StressResult getStressResult(List<Tasker> taskers) {
StressResult result = new StressResult();
List<Long> allTimes = new ArrayList<>();
for (Tasker tasker : taskers) {
List<Long> everyTaskerTimes = tasker.getEveryTimes();
allTimes.addAll(everyTaskerTimes);
}
System.out.println("所有请求响应时间:" + JSON.toJSONString(allTimes));
double avgTime = allTimes.stream().mapToLong(Long::longValue).average().getAsDouble();
result.setAvgTime(avgTime);

Collections.sort(allTimes);
int index = (int) Math.ceil(95 / 100.0 * allTimes.size());
result.setResponseTimeOfPercent95(allTimes.get(index - 1));
return result;
}


}



执行压测任务



@Test
public void test(){
StressTasker tasker = new StressTasker();
StressResult result = tasker.doWork(10,100,"http://www.baidu.com");
System.out.println(result.toString());
}



执行结果



然后再尝试压测其它类型的网站

  • 淘宝

地址:http://www.taobao.com



  • 京东

地址:http://www.jd.com



  • infoQ

地址:https://www.infoq.cn/



  • CSDN

地址:https://www.csdn.net



综合可得,网站性能由高到底:搜索引擎网站 > 购物网站 > 博客类网站



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

拈香(曾德政)

关注

还未添加个人签名 2018.04.29 加入

还未添加个人简介

评论

发布
暂无评论
性能压测