性能压测分析及其工具实现

本文是架构师训练营第 7 周课后作业题,

作业原题为:

  • 性能压测的时候,随着并发压力的增加,系统响应时间和吞吐量如何变化,为什么?
  • 用你熟悉的编程语言写一个  web  性能压测工具,输入参数:URL,请求总次数,并发数。输出参数:平均响应时间,95% 响应时间。用这个测试工具以 10  并发、100  次请求压测 www.baidu.com。

1、性能压测分析

性能测试是一个总称,具体可细分为性能测试、负载测试和压力测试三个阶段。性能压测时,系统资源与吞吐量(TPS)的关系如下图示。

性能压力测试
性能压力测试

随着测试并发数不断增长,系统吞吐量不断提升,相应地系统资源消耗也随着增长,当到图中 b 点时,性能达到最佳状态。此时若仍不断增大并发数,系统进入负载阶段,系统吞吐量提升不明显,但系统资源消耗仍然不断增长,到图中 c 点时,系统到达负载最大状态。若此时再增大并发数,系统进入压力阶段,系统吞吐量降低,系统资源仍不断增长,到达图中 d 点时,系统因资源消耗殆尽而崩溃。

相应地,并发数与系统响应时间关系如下图示。

响应时间与并发数
响应时间与并发数

随着并发数增长,在系统最佳性能运行区间,系统响应时间增长并不明显,当并发数超过系统最佳性能并发数时,系统响应时间随着并发数的增长而增长,到达图中 c 点时,系统牌最大负载状态,若此时并发数仍然增长,由于系统资源不足以应对并发处理,响应时间快速增长,直至最后系统崩溃。由此我们可以得出:吞吐量 = (1000 / 响应时间 ms) x  并发数。

2、性能压测工具实现

对于压测工具实现,关键要弄清楚 95% 响应时间的定义,即95% percentile : 统计学术语,如果将一组数据从小到大排序,并计算相应的累计百分位,则某一百分位所对应数据的值就称为这一百分位的百分位数。例如有 100 个请求, 每个请求的响应时间分别是 1-100 平均分布,平均响应时间为 1-100 的平均值,即 50.5。95% percentile , 即按从小到大排序,累计第 95 百分位,也就是 95 (即样本里 95% 的数据都不高于这个值)。

由于我比较熟悉 C#,所以使用了 C# 实现了性能压测工具,以下为总请求数为10000,分别以 10 和 100 的并发数压测百度的结果:

10并发数压测结果
10 并发数压测结果
100并发数压测结果
100 并发数压测结果

由上图可见 100 并发数和 10 并发数压测结果的性能指标相差非常大,不知是百度服务器问题还是我机器的问题,但从实际应用验证了第 1 部分的性能压测分析,现贴出关键代码如下:

namespace NMeter 
{ 
   public class ConcurrencyTest 
   { 
       private readonly IHttpClientFactory _httpClientFactory; 
       private ConcurrentQueue<(bool, long)> _queueResults; 
       private CancellationTokenSource _cancellationTokenSource;  //线程取消标记 
       private Task _task; //后台任务 
       public ConcurrencyTest(IHttpClientFactory httpClientFactory) 
       { 
           _httpClientFactory = httpClientFactory; 
           _queueResults = new ConcurrentQueue<(bool, long)>(); 
           _cancellationTokenSource = new CancellationTokenSource(); 
       } 
       public void Stop() 
       { 
           _cancellationTokenSource.Cancel(); 
       } 
       /// <summary> 
       /// 并发数 
       /// </summary> 
       /// <param name="totalRequests">请求总次数</param> 
       /// <param name="concurrent">并发数</param> 
       /// <param name="url">请求地址</param> 
       public void Start(int totalRequests, int concurrent, string url) 
       { 
           StartAnalysisTask(); 
           Parallel.For(0, totalRequests, new ParallelOptions() { MaxDegreeOfParallelism = concurrent }, 
           (i, state) => 
           { 
               var result = RequestAsync(url).GetAwaiter().GetResult(); 
               _queueResults.Enqueue(result); 
               Thread.Sleep(20); 
           }); 
       } 
       /// <summary> 
       /// 分析任务 
       /// </summary> 
       private void StartAnalysisTask() 
       { 
           if (_task == null) 
           { 
               _task = new Task(ViewHandler, _cancellationTokenSource.Token); 
           } 
           if (_task.Status != TaskStatus.Running) 
           { 
               _task.Start(); 
           } 
       } 
       private Action ViewHandler => () => 
       { 
           var totalRequests = 0;         //总请求数 
           var succeedRequests = 0;      //成功请求数 
           var failedRequests = 0;       //失败请求数 
           var successRate = 0.0f;      //请求成功率 
           var totalTime = 0l;          //总响应时间 
           var avgResponseTime = 0.0d;  //平均响应时间 
           var allTimes = new List<double>(); 
           while (true) 
           { 
               try 
               { 
                   if (_cancellationTokenSource.Token.IsCancellationRequested) 
                   { 
                       _queueResults = null; 
                       break; 
                   } 
                   else 
                   { 
                       if (!_queueResults.IsEmpty) 
                       { 
                           if (_queueResults.TryDequeue(out (bool, long) result)) 
                           { 
                               totalRequests++; 
                               totalTime += result.Item2; 
                               allTimes.Add(result.Item2); 
                               if (result.Item1) 
                               { 
                                   succeedRequests++; 
                               } 
                               else 
                               { 
                                   failedRequests++; 
                               } 
                               successRate = (float)succeedRequests / totalRequests; 
                               avgResponseTime = (float)totalTime / totalRequests; 
                               if (totalRequests % 10 == 0) 
                               { 
                                   var responseTime95 = (long)Percentile(allTimes, 0.95); 
                                   Console.WriteLine($"总请求数:{totalRequests},成功数:{succeedRequests},失败数:{failedRequests},请求成功率:{successRate},平均响应时间:{avgResponseTime},95%响应时间:{responseTime95}"); 
                               } 
                               Thread.Sleep(10); 
                           } 
                       } 
                       else 
                       { 
                           Thread.Sleep(TimeSpan.FromSeconds(30)); 
                       } 
                   } 
               } 
               catch (Exception ex) 
               { 
                   Console.WriteLine("请求处理异常" + ex.Message); 
               } 
           } 
       }; 
       /// <summary> 
       /// 计算时间线 
       /// </summary> 
       /// <param name="sequence"></param> 
       /// <param name="percentile"></param> 
       /// <returns></returns> 
       private double Percentile(IEnumerable<double> sequence, double percentile) 
       { 
           var elements = sequence.ToArray(); 
           Array.Sort(elements); 
           var length = elements.Length; 
           var realIndex = (length - 1) * percentile + 1; 
           if (realIndex == 1d) return elements[0]; 
           else if (realIndex == length) return elements[length - 1]; 
           else 
           { 
               var k = (int)realIndex; 
               var d = realIndex - k; 
               return elements[k - 1] + d * (elements[k] - elements[k - 1]); 
           } 
       } 
       /// <summary> 
       /// 发起请求(异步) 
       /// </summary> 
       /// <param name="requestUri">请求地址</param> 
       /// <returns>bool-是否成功/long-消耗时长</returns> 
       private async Task<(bool, long)> RequestAsync(string requestUri) 
       { 
           var httpClient = _httpClientFactory.CreateClient(); 
           httpClient.Timeout = TimeSpan.FromSeconds(60); 
           var monitor = new Stopwatch(); 
           monitor.Start(); 
           try 
           { 
               var response = await httpClient.GetAsync(requestUri); 
               monitor.Stop(); 
               var timeSpent = monitor.ElapsedMilliseconds; 
               return (response.IsSuccessStatusCode, timeSpent); 
           } 
           catch 
           { 
               monitor.Stop(); 
               var timeSpent = monitor.ElapsedMilliseconds; 
               return (false, timeSpent); 
           } 
       } 
   } 
}

《性能压测分析及其工具实现》的相关评论

发表评论

必填项已用 * 标记,邮箱地址不会被公开。