极客时间《架构师训练营》第七周课后作业
第一题
性能压测的时候,随着并发压力的增加,系统响应时间和吞吐量如何变化,为什么?贴一张经典的的性能测试曲线图:
![极客时间《架构师训练营》第七周课后作业](https://img.it610.com/image/info10/a2a81ec3b3794c7fa6947ec31a2e812a.jpg)
文章图片
性能测试曲线 并发量与响应时间和吞吐量的关系,通俗来说可以分为三个阶段:
- 轻负载阶段
这个阶段负载远未达到系统软硬件瓶颈,资源随时待命,请求被以最快的速度计算返回。响应时间保持平稳,几乎为最短消耗时间;吞吐量与负载也呈线性增长关系。
- 重负载阶段
该阶段系统无法再实现一次性处理所有响应了,受某些资源的限制,一些请求被阻塞在队列内,但软硬件依旧可以承受这种负载;响应时间开始单调递增,吞吐量保持相对稳定。
- 压垮阶段
这个阶段软硬件已无法承受这么大的负载了,系统资源消耗殆尽;响应时间垂直上涨,吞吐量呈断崖式下降。
用你熟悉的编程语言写一个 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 请求不能计算响应时间,所以魔改了一下:
- 给它的拦截器加了两个中间件:为 request 添加发起时间,为 response 计算响应时间
- 封装了 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 {
return axios(url).then((response: Response) => response.responseTime);
}
并发池
并发设计的思路很简单,就是建一个并发池;假设并发数为 10,就在这个并发池里放 10 个执行器——executor。TS 有个好处就是不用开多线程,天然的异步语言;直接
Promise.all
就可以并发执行池子里所有的 executor 了。const asyncPool: Promise[] = [];
// concurrency = 10;
while(concurrency--){
asyncPool.push( executor() );
}await Promise.all(asyncPool)
执行器
Executor 的设计,我用到了
Promise-then
可以串行执行异步函数的功能。通过递归调用,并发池里的 executor 就会不断地消费请求,直到完成目标请求数。type Request = () => Promise;
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[] = [];
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 }
,竟然比百度要快,总算我平日里没白忙活。推荐阅读
- 慢慢的美丽
- 《真与假的困惑》???|《真与假的困惑》??? ——致良知是一种伟大的力量
- 20170612时间和注意力开销记录
- 《跨界歌手》:亲情永远比爱情更有泪点
- 时间老了
- 诗歌:|诗歌: 《让我们举起世界杯,干了!》
- 期刊|期刊 | 国内核心期刊之(北大核心)
- 《魔法科高中的劣等生》第26卷(Invasion篇)发售
- 人间词话的智慧
- 《一代诗人》37期,生活,江南j,拨动心潭的一泓秋水