架构师训练营作业 -- Week 7
发布于: 2020 年 07 月 20 日
性能压测的时候,随着并发压力的增加,系统响应时间和吞吐量如何变化,为什么?
用你熟悉的编程语言写一个 web 性能压测工具,输入参数:URL,请求总次数,并发数。输出参数:平均响应时间,95% 响应时间。用这个测试工具以 10 并发、100 次请求压测 www.baidu.com。
性能测试曲线
性能测试曲线
由上图可见,性能测试可分成三个阶段,既轻载区,重载区和危险(拐点)区。我们假设轻载区的上边界对应的并发数为L, 重载区的上边界对应的并发数为H。当并发数低于L时,吞吐量(紫色)随并发数增长曾现线性增长。也就是说,当并发数小于L时,系统的性能在正常范围,可以很好地应对吞吐压力。当并发数大于L小于H时,对应系统性能曲线的重载区。此时可能系统的CPU,内存,或者网络等资源的一项或者多项的使用率达到了极限,已经有一部分用户需要等待更长时间获得响应。可以看到响应时间曲线(蓝色)斜率增加,而吞吐量也逐渐达到极限甚至开始下降。当并发数超过H,来到危险区时,所有系统资源都已经耗尽,可能已经引发系统阻塞,吞吐量迅速下降,而响应时间也呈指数级增加,系统性能严重下降,濒临崩溃。
性能测试的Java实现
Java实现如下:
import java.io.IOException;import java.util.ArrayList;import java.util.Collections;import java.util.Comparator;import java.util.List;import java.util.Scanner;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger;import java.util.stream.Collectors;import org.apache.commons.lang3.StringUtils;import okhttp3.Call;import okhttp3.Callback;import okhttp3.Dispatcher;import okhttp3.OkHttpClient;import okhttp3.Request;import okhttp3.Response;public class PerformanceTest { private static String targetUrl = "http://www.baidu.com"; private static int concurrentRequests = 100; private static int repeatTimes = 3; private static CountDownLatch batchLatch; private static List<String> statistics = Collections.synchronizedList(new ArrayList<>()); public static void main(String[] args) throws InterruptedException { // set arguments setArguments(); OkHttpClient httpClient = buildHttpClient(); // do performance test for (int i = 0; i < repeatTimes; i++) { batchLatch = new CountDownLatch(concurrentRequests); doPerformanceTesting(httpClient, i); batchLatch.await(5, TimeUnit.SECONDS); } // generate report generateReport(); System.exit(1); } private static void generateReport() { List<String[]> list = statistics.stream() .map(s -> s.split(",")) .collect(Collectors.toList()); Collections.sort(list, new Comparator<String[]>() { @Override public int compare(String[] o1, String[] o2) { if (o1 == null || o2 == null || o1.length < 4 || o2.length < 4) { return -1; } return Integer.valueOf(o1[2]) - Integer.valueOf(o2[2]); } }); double avg = list.stream() .collect(Collectors.summarizingInt(e -> Integer.valueOf(e[2]))) .getAverage(); System.out.println("Average response time: " + avg + "ms"); System.out.println("95th percentile response time: " + list.get( (int)Math.floor(list.size() * .95f) )[2] + "ms"); long failureCnt = list.stream().filter(e -> "False".equals(e[3])).count(); System.out.println("Failure rate: " + (failureCnt / list.size() * 100) + "%"); } private static void doPerformanceTesting(OkHttpClient httpClient, int batchNumber) { Request req = new Request.Builder().get().url(targetUrl).build(); for (int i = 0; i < concurrentRequests; i++) { httpClient.newCall(req).enqueue(new Callback() { @Override public void onResponse(Call call, Response response) throws IOException { try { batchLatch.countDown(); String stat = String.format("%d,%s,%d,%s", batchNumber, Thread.currentThread().getId(), response.receivedResponseAtMillis() - response.sentRequestAtMillis(), "True"); System.out.println(stat); statistics.add(stat); } finally { response.close(); } } @Override public void onFailure(Call call, IOException e) { batchLatch.countDown(); String stat = String.format("%d,%s,%d,%s", batchNumber, Thread.currentThread().getName(), -1, "False"); System.out.println(stat); statistics.add(stat); } }); } } private static OkHttpClient buildHttpClient() { OkHttpClient.Builder builder = new OkHttpClient.Builder(); ExecutorService exec = new ThreadPoolExecutor(concurrentRequests, concurrentRequests, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); Dispatcher d = new Dispatcher(exec); builder.dispatcher(d); OkHttpClient httpClient = builder.build(); httpClient.dispatcher().setMaxRequestsPerHost(concurrentRequests); return httpClient; } private static void setArguments() { // set target url System.out.println("Please input target URL (http://www.baidu.com by default): "); Scanner scanner = new Scanner(System.in); String url = scanner.nextLine(); if (StringUtils.isNotBlank(url)) { targetUrl = url; } System.out.println("Target URL: " + targetUrl); System.out.println(); // set number of concurrent requests System.out.println("Please input number of concurrent requests: "); int cnt = scanner.nextInt(); if (cnt > 0) { concurrentRequests = cnt; } System.out.println("Number of concurrent requests: " + concurrentRequests); System.out.println(); // set repeat times System.out.println("Please input repeat times: "); int times = scanner.nextInt(); if (times > 0) { repeatTimes = times; } System.out.println("Repeat times: " + repeatTimes); System.out.println(); } }
输出结果:
Please input target URL (http://www.baidu.com by default): Target URL: http://www.baidu.comPlease input number of concurrent requests: 10Number of concurrent requests: 10Please input repeat times: 100Repeat times: 1000,13,57,True0,18,59,True...99,22,31,TrueAverage response time: 38.991ms95th percentile response time: 37msFailure rate: 0
划线
评论
复制
发布于: 2020 年 07 月 20 日 阅读数: 54
版权声明: 本文为 InfoQ 作者【吴炳华】的原创文章。
原文链接:【http://xie.infoq.cn/article/05be5a9a8f8f34cd8fe2a8036】。文章转载请联系作者。
吴炳华
关注
还未添加个人签名 2020.04.08 加入
还未添加个人简介
评论