Bootstrap

架构师训练营 Week7 - 课后作业

并发数,响应时间,吞吐量

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

1,概念:

  • 响应时间:系统从收到请求到响应发出所占用的时间

  • 并发数:系统能同时处理的请求数量

  • 并发请求数

  • 在线用户数

  • 系统用户数

  • 吞吐量:单位时间内系统处理的请求数量。

  • QPS: 每秒查询数量

  • TPS: 每秒交易数量

  • HPS:每秒HTTP数量

2,关系:

整个系统从单机(垂直伸缩), 集群(水平伸缩)方面资源是有限的。同时站在成本收益比的角度,资源分配和最大可处理的请求达到一个比较好的平衡(理想情况下)。 借用课上的几张图:

  • 并发数 VS 吞吐量:

  • 初始阶段线性增加(a->b),系统资源充足。 随着并发数的增加吞吐量增加,系统资源逐步被占满。(a->b)

  • 吞吐量增加变慢:请求数量增加,系统需要分配一部分资源处理调度,等待资源释放。吞吐量增加速度减慢。

  • 临界点(c),当并发数达到某个数量级时,系统资源(CPU,数据库连接,JVM堆内存/GC...)被充分占用,不能同时响应新的请求。此时就会出现请求等待排队。

  • 超过临界点(c->d), 当有更多的请求堆积等待,系统需要额外分出一部分资源去处理额外的等待请求。对于单机处理会出现频繁的线程切换,Full GC.这些处理都会有消耗,导致吞吐量下降。

  • 出错/崩溃:当请求数量大大超出系统处理能力,就会出现请求超时。系统内部资源处理不当会导致OOM (GC失败)或者系统down机。

  • 并发数VS响应时间yu

与吞吐量对应。

初始状态:并发请求都可以分配到足够的系统资源处理,响应时间基本一致

响应时间增加:随着资源被占用,更多的请求不能立马被处理,平均响应市场增加。

临界点:当达到某个临界点,系统为处理额外的消耗占用的资源时间增加。单个请求等待的时常幅度增大

出错/崩溃:达到某个请求数量系统开始崩溃,响应时间达到最大值。系统超时,宕机。

Web 性能压测工具 - LoadRunner

1. 执行

> java -jar .\HttpLoadRunner-1.0-SNAPSHOT.jar http://www.baidu.com c10 t100

Testing Url: http://www.baidu.com
Concurrent:  10, Total Num:  100
================= Load Runner Result / Million Seconds ==================
Executed Num: 100, Failure Num: 0
Avg Time: 63.21, 95% Avg Time: 52.53, 95% Index: 95
Total Duration: 6401, Sum of TimeSpent:6321

> java -jar .\HttpLoadRunner-1.0-SNAPSHOT.jar http://www.baidu.com c100 t100

Testing Url: http://www.baidu.com
Concurrent: 100, Total Num:  100
================= Load Runner Result / Million Seconds ==================
Executed Num: 100, Failure Num: 0
Avg Time: 58.74, 95% Avg Time: 48.38, 95% Index: 95
Total Duration: 5994, Sum of TimeSpent:5874

> java -jar .\HttpLoadRunner-1.0-SNAPSHOT.jar http://www.baidu.com c100 t1000

Testing Url: http://www.baidu.com
Concurrent: 100, Total Num:  1000
================= Load Runner Result / Million Seconds ==================
Executed Num: 1000, Failure Num: 0
Avg Time: 48.64, 95% Avg Time: 45.74, 95% Index: 950
Total Duration: 49015, Sum of TimeSpent:48644

2. 源代码

package io.http;

import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

import java.util.Arrays;
import java.util.concurrent.*;

public class LoadRunner {
    private static int concurrentNum = 10;
    private static int totalNum = 100;
    private static String url = "http://www.baidu.com";
    public static void main(String[] args) throws Exception{
        //init member variable;
        init(args);

        System.out.printf("Testing Url: %s\n", url);
        System.out.printf("Concurrent: %3d, Total Num: % d\n", concurrentNum, totalNum);

        long startTime = System.currentTimeMillis();
        int failureNum = 0;
        long totalTime = 0;
        long sumOfTimeSpent = 0;
        double totalAvg = 0;
        double percAvg = 0;
        long[] durations = new long[totalNum];
        ExecutorService executor = Executors.newFixedThreadPool(concurrentNum);

        try{
            for (int i = 0; i < totalNum; i++){
                FutureTask requestTask = getLongFutureTask(i);
                executor.submit(requestTask);
                Long timeSpent = requestTask.get(30, TimeUnit.SECONDS);
                if (timeSpent != -1) {
                    durations[i] = timeSpent;
                    sumOfTimeSpent += timeSpent;
                } else {
                    durations[i] = Long.MAX_VALUE;
                    failureNum++;
                }
            }
            Arrays.sort(durations);
            totalAvg = Arrays.stream(durations).average().orElse(Double.NaN);
            int percIndex = Math.round((totalNum-failureNum) * 95/100);
            percAvg = Arrays.stream( Arrays.copyOfRange(durations, 0, percIndex)).average().orElse(Double.NaN);
            totalTime = System.currentTimeMillis() - startTime;
            System.out.println("================= Load Runner Result / Million Seconds ==================");
            System.out.printf("Executed Num: %d, Failure Num: %d\n", totalNum, failureNum);
            System.out.printf("Avg Time: %.2f, 95%% Avg Time: %.2f, 95%% Index: %d\n", totalAvg, percAvg, percIndex);

            System.out.printf("Total Duration: %d, Sum of TimeSpent:%d\n", totalTime, sumOfTimeSpent);
        } finally {
            executor.shutdown();
        }
    }

    private static FutureTask getLongFutureTask(int i) {
        FutureTask requestTask = new FutureTask(new Callable() {
            public Long call() throws Exception {
                Long start = System.currentTimeMillis();
                Long duration = -1L;

                //HTTP client call
                CloseableHttpClient httpclient = HttpClients.createDefault();
                HttpGet httpGet = new HttpGet(url);
                CloseableHttpResponse response = httpclient.execute(httpGet);
                try {
                    HttpEntity responseEntity = response.getEntity();
                    if(HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
                        duration = System.currentTimeMillis() -  start;
                    } else {
                        // other case consider as failed.
                        duration = -1L;
                    }
//                    System.out.printf("Thread: %s, Duration: %s\n", Thread.currentThread().getName(), duration);
                } finally {
                    response.close();
                }
                return duration;
            }
        }) ;
        return requestTask;
    }

    static void init(String[] args) {
        if(args == null) return;

        for (String arg : args) {
            if (arg == null || arg.length() <= 1) continue;

            //input url
            if (arg.startsWith("http://")) {
                url = arg;
                continue;
            }

            String paramName = arg.substring(0,1);
            String paramValue = arg.substring(1);
            //input concurrent number
            if (paramName.equalsIgnoreCase("c") && paramValue.matches("[0-9]+")){
                concurrentNum = Integer.parseInt(arg.substring(1));
                continue;
            }
            //input duration seconds
            if (paramName.equalsIgnoreCase("t") && paramValue.matches("[0-9]+")){
                totalNum = Integer.parseInt(arg.substring(1));
                continue;
            }
        }
    }
}