压力测试
想要优化性能,首先需要做性能检查。对于HTTP服务性能的检测,首先需要做的是压力测试,即在高并发的情况下该HTTP服务的表现/性能如何。首先了解一下压力测试相关的参数的概念:
- 吞吐率(Requests per second)
概念:服务器并发处理能力的量化描述,单位是reqs/s,指的是某个并发用户数下单位时间内处理的请求数。某个并发用户数下单位时间内能处理的最大请求数,称之为最大吞吐率。
计算公式:总请求数 / 处理完成这些请求数所花费的时间
- 并发连接数(The number of concurrent connections)
概念:某个时刻服务器所接受的请求数目,简单的讲,就是一个会话。
- 并发用户数(The number of concurrent users,Concurrency Level)
概念:要注意区分这个概念和并发连接数之间的区别,一个用户可能同时会产生多个会话,也即连接数。
- 用户平均请求等待时间(Time per request)
计算公式:处理完成所有请求数所花费的时间/ (总请求数 / 并发用户数)
- 服务器平均请求等待时间(Time per request: across all concurrent requests)
计算公式:处理完成所有请求数所花费的时间 / 总请求数。可以看到,它是吞吐率的倒数。同时,它也等于用户平均请求等待时间/并发用户数。
ab是apache自带的压力测试工具。ab非常实用,它不仅可以对apache服务器进行网站访问压力测试,也可以对或其它类型的服务器进行压力测试。
//输入指令
ab -c200 -n1600 http//127.0.0.1:3000/download
复制代码
当启动node服务之后,输入上面的指令,其中-c表示并发数,-n表示请求数,后面的网址表示我们要进行压力测试的页面。结果如下图所示:
Document Path表示返回包的路径,Document Length表示返回包的长度,Concurrency Level表示并发数,Time taken for tests表示压测所花时间,Complete requests表示完成的请求数。Total transferred和HTML transferred表示总共的传输量。Requests per second表示每秒服务器能承受的请求数,Time per request表示用户平均请求等待的时间,Time per request:accross all concurrent requests表示每次请求的平均耗时,Transfer rate表示服务器的最大吞吐量,表示服务器每秒最大能接收或发送多少的数据。
判断服务器性能的瓶颈在哪里:如果吞吐量正好就是网卡能够承受的最大通信量,说明我们服务的瓶颈是在网卡,而不是在计算机的其他部件上。同时可以利用Linux指令top查看电脑的CPU和内存的使用情况,可以在压测的同时检测电脑CPU和内存的情况,可以检测性能的瓶颈是否在CPU或内存上;iostat用来检测I/O设备的带宽,可以判断性能的瓶颈是否在设备的I/O上。还有一个可能是瓶颈在后端,比如说我们服务每秒能发送600个请求,但是后端每秒只能处理300个请求,那么服务性能的瓶颈就在后端了。
找到服务器的性能瓶颈之后,我们就能针对这些瓶颈进行优化:网卡不行就换带宽更大的网卡,内存不行就换更大的内存,后端处理能力不能就和后端同学沟通让后端进行优化。
node.js性能分析工具
- node.js自带的profile
使用命令:node –prof entry.js,会在压测过程中生成一个压测报告文件,之后我们使用命令:node –prof-process index.log > profile.txt,会将压测文件输出到profile.txt文件中,index.log是前面指令生成的压测文件名。profile.txt文件中会统计各个不同操作的耗时以及CPU的占用率
- Chrome devtool。由于node.js是基于V8引擎的,所以可以利用Chrome devtool来查看服务性能。
首先输入指令:node –inspect-brk entry.js,表示在启动调试的同时会暂停我们的服务,然后在Chrome浏览器中输入:chrome://inspect,页面中的target表示找到的node.js的debugger协议,点击inspect进入到Chrome的调试模式。里面有console输出控制台,source里是我们node.js 代码,profile这可以监控我们的CPU性能。
代码优化
我们利用Chrome devtool进行CPU性能的监控,并进行压力测试,得出如下的结果。然后就可以根据具体的测试报告来进行代码优化。
- JavaScript代码性能优化
可以看到readFileSync消耗的CPU比较大,那么查看代码中用到readFileSync的地方。这是我们会发现,因为我们在中间件中调用了readFileSync方法,然后每次请求的时候都会重新执行中间件的逻辑,也就是都会执行readFileSync方法,所以有多少次请求就会执行多少次readFileSync方法,所以readFileSync导致了大量的CPU性能消耗。
app.use(
mount('/',function(ctx){
ctx.body = fs.readFileSync(__dirname + '/index.heml','utf-8');
})
);
复制代码
这时可以将readFileSync的调用提出到中间件的外面,在里面复用执行的结果,这样就可以降低readFileSync对于CPU的消耗。
const str = fs.readFileSync(__dirname + '/index.heml','utf-8');
app.use(
mount('/',function(ctx){
ctx.body = str;
})
);
复制代码
修改代码后,利用Chrome devtool进行CPU性能的监控,并进行压力测试。我们可以看到上张图里readFileSync的占比没有了,取而代之的是readFileSync后面的几位CPU占比比较大的操作,由此可以验证将文件的读取提取到中间件外面之后,确实可以降低readFileSync对于CPU的消耗。
JavaScript优化性能的本质:1、减少不必要的计算;如将很多小图片合并成大图片上传,减少HTTP总请求数,能减少大量的TCP连接与断开、HTTP编解码等的消耗。2、空间换时间;比如把一些重复计算的结果缓存起来,那么在下一次需要计算的时候就可以直接使用缓存的结果,省下大量的计算量,从而达到性能的优化。
总的来说,就是在优化的时候思考:在看到一行代码时,思考在用户感知到的时间里面,这个计算是否是必要?能否在其他时间做这个计算,达到用空间换时间的优化呢?
思考之后会发现,HTTP性能优化的准则就是:提前计算。尽可能的把HTTP服务阶段的计算挪到node.js启动阶段进行,就能达到不错的性能优化效果。