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

这是老师的ppt里提供的两个图。性能测试学习过程中,常见的图也包括下面这个

https://time.geekbang.org/column/article/178076

在这个图中,定义了三条曲线、三个区域、两个点以及三个状态描述。

三条曲线:吞吐量的曲线(紫色)、使用率 / 用户数曲线(绿色)、响应时间曲线(深蓝色)。

三个区域:轻负载区(Light Load)、重负载区(Heavy Load)、塌陷区(Buckle Zone)。

两个点:最优并发用户数(The Optimum Number of Concurrent Users)、最大并发用户数(The Maximum Number of Concurrent Users)。

三个状态描述:资源饱和(Resource Saturated)、吞吐下降(Throughput Falling)、用户受影响(End Users Effected)。

从图中可以看到,随着负载的增大,随着并发用户的增长,系统响应时间,吞吐率的变化。

当然,上图有一些不精确的地方,简化出另一个图形,以说明更直接一点的关系。如下所示:

在 TPS 增加的过程中,响应时间一开始会处在较低的状态,也就是在 A 点之前。接着响应时间开始有些增加,直到业务可以承受的时间点 B,这时 TPS 仍然有增长的空间。再接着增加压力,达到 C 点时,达到最大 TPS。我们再接着增加压力,响应时间接着增加,但 TPS 会有下降(请注意,这里并不是必然的,有些系统在队列上处理得很好,会保持稳定的 TPS,然后多出来的请求都被友好拒绝)。最后,响应时间过长,达到了超时的程度

压测程序

时间比较紧张,参考https://github.com/danielzhou/goablite

go routine http.Get(*u) 获取结果

/*

 */

package main

import (
	"flag"
	"fmt"
	"net/http"
	"os"
	"runtime"
	"sync"
	"time"
)

// 参数
var (
	n = flag.Int("n", 2, "Number of requests to run.")
	c = flag.Int("c", 1, "Number of requests to run concurrently.\nTotal number of requests cannot be smaller than the concurrency level.")
	p = flag.Int("p", runtime.GOMAXPROCS(-1), "Number of used cpu cores")
	u = flag.String("u", "", "the url you want to connect.")
)

// 结果
var (
	success = 0.0
	failure = 0.0
	useTime = 0.0
)

// 帮助信息
var usage = `Usage: hey [options...] <url>

Options:
  -n  Number of requests to run. Default is 20.
  -c  Number of requests to run concurrently. Total number of requests cannot
      be smaller than the concurrency level. Default is 5.
  -p  Number of used cpu cores.(default for current machine is %d cores)
`

var wg sync.WaitGroup

// 主函数
func main() {
	flag.Usage = func() {
		fmt.Fprint(os.Stderr, fmt.Sprintf(usage, runtime.NumCPU()))
	}
	flag.Parse()

	// 判断参数是否正常
	if flag.NFlag() < 1 {
		usageAndExit("")
	}
	runtime.GOMAXPROCS(*p)
	num := *n
	conc := *c
	url := *u

	if url == "" || len(url) == 0 {
		usageAndExit("Please input the url you want to connect.")
	}
	if num <= 0 || conc <= 0 {
		usageAndExit("-n and -c cannot be smaller than 1.")
	}

	if num < conc {
		usageAndExit("-n cannot be less than -c.")
	}
	if *p > runtime.NumCPU() {
		fmt.Printf("your computer have %d CPU cores\n", runtime.NumCPU())
		usageAndExit("-p cannot bigger than that your computer have.")

	}

	startTime := time.Now().UnixNano()

	// 并发开始
	for i := 0; i < conc; i++ {
		wg.Add(1)
		go run(num / conc)
	}
	fmt.Println("主程序开始wait")
	wg.Wait()
	endTime := time.Now().UnixNano()
	useTime = float64(endTime-startTime) / 1e9

	// 输出结果
	fmt.Println("Plan_Total:", *n)
	fmt.Println("Concurrency Level:", *c)
	fmt.Println("CPU Core:", *p)
	fmt.Println()
	fmt.Println("Complete requests:", success)
	fmt.Println("Failed requests:", failure)
	// fmt.Println("SuccessRate:", fmt.Sprintf("%.2f", ((success/total)*100.0)), "%")
	fmt.Println("UseTime:", fmt.Sprintf("%.4f", useTime), "s")
	fmt.Println("Requests per second:", fmt.Sprintf("%.4f", float64(*n)/useTime))

}

// 运行每个客户端
func run(num int) {

	defer wg.Done()

	no := 0.0
	ok := 0.0

	for i := 0; i < num; i++ {
		resp, err := http.Get(*u)

		if err != nil {
			no += 1
			continue
		}

		defer resp.Body.Close()

		if resp.StatusCode != 200 {
			no += 1
			continue
		}

		ok += 1
		continue
	}

	success += ok
	failure += no
	// total += float64(num)

}

// 提示信息
func usageAndExit(msg string) {
	if msg != "" {
		fmt.Fprintf(os.Stderr, msg)
		fmt.Fprintf(os.Stderr, "\n\n")
	}
	flag.Usage()
	fmt.Fprintf(os.Stderr, "\n")
	os.Exit(1)
}
分别10并发和100并发,在100并发的时候部分返回结果错误,

100请求,10并发
Plan_Total: 100
Concurrency Level: 10
CPU Core: 4

Complete requests: 100
Failed requests: 0
UseTime: 31.7410 s
Requests per second: 3.1505
Exiting.

Debugger finished with exit code 0

200请求,100并发:

主程序开始wait
Plan_Total: 200
Concurrency Level: 100
CPU Core: 4

Complete requests: 185
Failed requests: 15
UseTime: 283.5817 s
Requests per second: 0.7053
Exiting.

Debugger finished with exit code 0

补一个java版本的

package com.zhk.metricscollections.request;

import lombok.Data;

/**
 * @author zhuhk
 * @create 2020-07-23 9:49 上午
 * @Version 1.0
 **/
@Data
public class RequestInfo {
    private String apiName;  // 扩展
    private double responseTime; //扩展
    private long timestamp;
}
package com.zhk.metricscollections.service;

import com.zhk.metricscollections.request.RequestInfo;
import java.io.IOException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

/**
 * @author zhuhk
 * @create 2020-07-23 11:05 下午
 * @Version 1.0
 **/
public class HttpHandler {
    public static RequestInfo  doGet(String url) throws IOException {
        RequestInfo requestInfo = new RequestInfo();
        long start = System.currentTimeMillis();
        CloseableHttpClient httpclient = HttpClients.createDefault();
        try {
            HttpGet httpget = new HttpGet(url);

            System.out.println("Executing request " + httpget.getRequestLine());

            // Create a custom response handler
            ResponseHandler<String> responseHandler = new ResponseHandler<String>() {

                @Override
                public String handleResponse(
                        final HttpResponse response) throws ClientProtocolException, IOException {
                    int status = response.getStatusLine().getStatusCode();
                    if (status >= 200 && status < 300) {
                        HttpEntity entity = response.getEntity();
                        return entity != null ? EntityUtils.toString(entity) : null;
                    } else {
                        throw new ClientProtocolException("Unexpected response status: " + status);
                    }
                }

            };
            String responseBody = httpclient.execute(httpget, responseHandler);
            requestInfo.setTimestamp(System.currentTimeMillis() - start);

//            System.out.println("----------------------------------------");
//            System.out.println(responseBody);
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            httpclient.close();
        }

        return requestInfo;
    }
}
package com.zhk.metricscollections.service;

import com.zhk.metricscollections.request.RequestInfo;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * @author zhuhk
 * @create 2020-07-24 7:29 上午
 * @Version 1.0
 **/
public class PressCreater {
    private int n = 1;
    private int c = 1;
    private String url;

    public PressCreater(int n, int c, String url) {
        this.n = n;
        this.c = c;
        this.url = url;
    }

    public List<RequestInfo> doPress() throws InterruptedException {
        List<RequestInfo> respList = new ArrayList<>();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(c, c,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());

        IntStream.range(1,n).forEach(i -> {
            executor.execute(() -> {
                RequestInfo resp = null;
                try {
                    resp = HttpHandler.doGet(this.url);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                respList.add(resp);
            });
        });
        while (executor.getActiveCount() > 0) {
            Thread.sleep(100);
        }
        executor.shutdown();
        return respList;
    }

    public long getAvgRespTime(List<RequestInfo> respList) {
        return Optional.ofNullable(respList).orElse(new ArrayList<>()).stream()
                .map(RequestInfo::getTimestamp)
                .collect(Collectors.averagingLong(Long::longValue)).longValue();
    }

    public long getAvgRespTime(List<RequestInfo> respList, int percent) {
        long avgRespTime = 0L;
        Optional<List<RequestInfo>> respTimesOptional = Optional.ofNullable(respList);
        if (respTimesOptional.isPresent()) {
            avgRespTime = respTimesOptional.get().stream()
                    .map(RequestInfo::getTimestamp)
                    .sorted()
                    .collect(Collectors.toList())
                    .get(Math.max(Math.floorDiv(respList.size() * percent, 100) - 1, 0));
        }
        return avgRespTime;
    }

    public void print(List<RequestInfo> respList) {
        System.out.println(String.format("并发 %d 访问 %s,总计访问 %d 次,压测结果:", this.c, this.url, this.n));
        System.out.println(String.format("平均响应时间: %d ms", this.getAvgRespTime(respList)));
        System.out.println(String.format("95%s响应时间: %d ms", "%", this.getAvgRespTime(respList, 95)));
    }
}
package com.zhk.metricscollections.service;

import com.zhk.metricscollections.request.RequestInfo;
import org.junit.jupiter.api.Test;

import java.util.List;

class PressCreaterTest {

    @Test
    public void testPressCeater() throws InterruptedException {
        String url = "http://www.baidu.com";
        int c = 10;
        int n = 100;
        PressCreater pressCreater = new PressCreater(n, c, url);
        List<RequestInfo> respList = pressCreater.doPress();
        pressCreater.print(respList);
    }
}
并发 10 访问 http://www.baidu.com,总计访问 100 次,压测结果:
平均响应时间: 1462 ms
95%响应时间: 7657 ms