美文网首页后端基础
极客时间《架构师训练营》第七周课后作业

极客时间《架构师训练营》第七周课后作业

作者: anOnion | 来源:发表于2020-07-21 17:54 被阅读0次

    第一题

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

    贴一张经典的的性能测试曲线图:

    性能测试曲线

    并发量与响应时间和吞吐量的关系,通俗来说可以分为三个阶段:

    1. 轻负载阶段

      这个阶段负载远未达到系统软硬件瓶颈,资源随时待命,请求被以最快的速度计算返回。响应时间保持平稳,几乎为最短消耗时间;吞吐量与负载也呈线性增长关系。

    2. 重负载阶段

      该阶段系统无法再实现一次性处理所有响应了,受某些资源的限制,一些请求被阻塞在队列内,但软硬件依旧可以承受这种负载;响应时间开始单调递增,吞吐量保持相对稳定。

    3. 压垮阶段

      这个阶段软硬件已无法承受这么大的负载了,系统资源消耗殆尽;响应时间垂直上涨,吞吐量呈断崖式下降。

    第二题

    用你熟悉的编程语言写一个 web 性能压测工具,输入参数:URL、请求总次数、并发数。输出参数:平均响应时间、95%响应时间。用这个测试工具以 10 并发、100 次请求压测百度(或其他网站)

    我是前端开发,用 Typescript(Javascript 超集)写并发有天然的语言优势,哈哈。

    预热

    先准备两个方法,即响应时间的平均数和 95% 数:

    // utils.ts
    export function response_avg(arr: number[]): number {
      return arr.reduce((p, c) => p + c, 0) / arr.length;
    }
    
    export function response_95(arr: number[]): number {
      arr.sort((a, b) => a - b);
      const idx: number = (arr.length * 0.95) | 0;
      return arr[idx];
    }
    

    fetch 方法

    请求函数我用到了axios库,但是原生的 axios 请求不能计算响应时间,所以魔改了一下:

    1. 给它的拦截器加了两个中间件:为 request 添加发起时间,为 response 计算响应时间
    2. 封装了 axios,export fetch 方法并返回本次请求的响应时间
    // fetch.ts
    import axios from "axios";
    
    axios.interceptors.request.use( (config: Config) => {
      config.meta = { requestStartedAt: new Date().getTime() }
      return config;
    })
    
    axios.interceptors.response.use((response: Response) => {
      response.responseTime = new Date().getTime() - response.config.meta.requestStartedAt;
      return response;
    });
    
    export function fetch(url: string): Promise<number> {
      return axios(url).then((response: Response) => response.responseTime);
    }
    

    并发池

    并发设计的思路很简单,就是建一个并发池;假设并发数为 10,就在这个并发池里放 10 个执行器——executor。TS 有个好处就是不用开多线程,天然的异步语言;直接 Promise.all 就可以并发执行池子里所有的 executor 了。

    const asyncPool: Promise<number[]>[] = [];
    // concurrency = 10;
    while(concurrency--){
      asyncPool.push( executor() );
    }
    
    await Promise.all(asyncPool)
    

    执行器

    Executor 的设计,我用到了 Promise-then 可以串行执行异步函数的功能。通过递归调用,并发池里的 executor 就会不断地消费请求,直到完成目标请求数。

    type Request = () => Promise<number>;
    
    function executor(requests: Request [], rts: number[] = []) {
    
      const req: Request  = requests.pop();
    
      if(req === undefined) return Promise.resolve(rts)
    
      return req().then((rt) => executor(requests, [...rts, rt]));
    }
    
    • 参数 requests 是所有请求的集合——函数数组;Request 是一个别名类型,代表一个返回 Promise 的函数。所有的 executor 都会竞争执行这个数组里的请求,直至为 0。

    • 另一个参数 rts 就是 Response Times 的缩写,目的是保存每个请求的响应时间。

    测试代码

    把上述代码组合起来,就得到了一个统计输出函数了:

    function runConcurrencyTest(
      args: {url: string, concurrency: number, times: number}
      ) {
    
      const requests: Request[] = [...Array(args.times)].fill(() => fetch(args.url));
      const asyncPool: Promise<number[]>[] = [];
      let limit: number = args.concurrency;
    
      while( limit-- ) {
        asyncPool.push( executor(requests) )
      }
    
      return Promise.all(asyncPool)
        .then((rts) => {
    
          const responseTimes: number[] = rts.flat()
    
          return {
            avg: response_avg(responseTimes),
            res_95: response_95(responseTimes),
          };
        });
    }
    

    附上 github 源码:main.ts

    最后,我试了一下百度的响应结果:{ avg: 2511.72, res_95: 2854 }, 平均要 2 秒多;感觉挺慢的,看了一下浏览器加载时间也差不多,应该是百度需要加载的资源太多了吧。我又测了一下自家的网站:{ avg: 473.87, res_95: 657 },竟然比百度要快,总算我平日里没白忙活。

    相关文章

      网友评论

        本文标题:极客时间《架构师训练营》第七周课后作业

        本文链接:https://www.haomeiwen.com/subject/qorwkktx.html