额外一点,在上面可以看到我们在注释的地方也使用了一个stream流的形式进行了返回,如果我们返回的是文件,第一种的同步读取其实相对更耗时,如果是个大的文件,会在内存空间先去存储,拿到全部的文件之后才会一次返回,这样的性能包括内存占用在文件较大的时候更为明显。所以如果我们做的是ssr或者文件下载之类的东西我们都可以以这样流的形式去做更加高效,至此,我们已经有了一个简单的http服务了,接下来我们对齐进行扩展。
性能测试、压测
首先我们需要借助测试工具模拟在高并发情况下的状态,这里我推荐两种压测工具。
ab 官方文档
webbench
autocannon
本次我们使用ab压测工具来进行接下来的操作,所以这里为大家介绍一下ab。那么ab呢是apache公司的一款工具,mac系统是自带这个工具的,安装教程呢大家就自行去查看,当然mac自带的ab是有并发限制的。
然后我们先随便来一条简单的命令再为大家分析一下具体的参数
ab -c200 -n1600 http://127.0.0.1:3000/index
上面这条命令的意思呢就是测试接口地址http://127.0.0.1:3000/index对齐每秒200个请求,并请求总数1600次这样的一个压测,然后我们看看这个工具的其他参数吧
上面的东西呢其实已经很直观了,最开头的部分就是每秒请求成功了多少个,其次就是请求地址、端口、路径、大小、这些其实不是很重要,我们在浏览器中自己也可以看到,我们主要需要注意的性能指标是下面这些参数:
Complete requests: 1600 # 请求完成成功数 这里判断的依据是返回码为200代表成功
Failed requests: 0 # 请求完成失败数
Total transferred: 8142400 bytes # 本次测试传输的总数据
HTML transferred: 7985600 bytes
Requests per second: 2188.47 [#/sec] (mean) # QPS 每秒能够处理的并发量
Time per request: 91.388 [ms] (mean) # 每次请求花费的平均时常
Time per request: 0.457 [ms] # 多久一个并发可以得到结果
Transfer rate: 10876.09 [Kbytes/sec] received # 吞吐量 每秒服务器可以接受多少数据传输量
一般而言我们只需要注意最后四条即可,首先可以直观知道当前服务器能承受的并发,同时我们可以知道服务器的瓶颈来自于哪里,如何分析呢?如果这里的吞吐量刚好是我们服务器的网卡带宽一样高,说明瓶颈来自于我们的带宽,而不是来自于其他例如cpu,内存,硬盘等等,那么我们其他的如何查看呢,我们可以借助这两个命令
top 监控计算机cpu和内存使用情况
iostat 检测io设备的带宽的
我们就可以在使用ab压测的过程中实时查看服务器的状态,看看瓶颈来自于「cpu」、「内存」、「带宽」等等对症下药。
当然存在一种特殊情况,很多场景下「NodeJs」只是作为「BFF」这个时候假如我们的「Node」层能处理600的「qps」但是后端只支持300,那么这个时候的瓶颈来自于后端。
在某些情况下,负载满了可能也会是「NodeJs」的计算性能达到了瓶颈,可能是某一处的代码所导致的,我们如何去找到「NodeJs」的性能瓶颈呢,这一点我们接下来说说。
如果对python自动化测试、web自动化、接口自动化、移动端自动化、面试经验交流等等感兴趣的测试人,可以 点这自行获取…
Nodejs性能分析工具
profile
「NodeJs」自带了「profile」工具,如何使用呢,就是在启动的时候加上**–prof**即可,node --prof index.js,当我们启动服务器的时候,目录下会立马生成一个文件isolate-0x104a0a000-25750-v8.log,我们先不用关注这个文件,我们重新进行一次15秒的压测:
ab -c50 -t15 http://127.0.0.1:3000/index
等待压测结束后,我们的这个文件就发生了变化,但是里面的数据很长我们还需要进行解析
使用「NodeJs」自带的命令 node --prof-process isolate-0x104a0a000-25750-v8.log > profile.txt
这个命令呢就是把我们生成的日志文件转为txt格式存在当前目录下,并且更为直观可以看到,但是这种文字类型的对我来说也不是足够方便,我们大致说说里面的内容吧,就不上图了,里面包含了,里面有js,c++,gc等等的各种调用次数,占用时间,还有各种的调用栈信息等等,这里你可以手动实现之后看看。
总体来说还是不方便查看,所以我们采用另一种方式。
chrome devtools
因为我们知道「NodeJs」是基础「chrome v8引擎」的「javascript运行环境」,所以我们调试「NodeJs」也是可以对「NodeJs」进行调试的。这里我们要使用新的参数–inspect, -brk代表启动调试的同时暂停程序运行,只有我们进入的时候才往下走。
node --inspect-brk index.js
(base) xiaojiu@192 node-share % node --inspect-brk index.js
Debugger listening on ws://127.0.0.1:9229/e9f0d9b5-cdfd-45f1-9d0e-d77dfbf6e765
For help, see: https://nodejs.org/en/docs/inspector
运行之后我们看到他就告诉我们监听了一个websocket,我们就可以通过这个ws进行调试了。
我们进入到「chrome浏览器」然后在地址栏输入chrome://inspect
然后我们可以看到other中有一个「Target」,上面输出了版本,我们只需要点击最后一行的那个「inspect」就可以进入调试了。进入之后我们发现,上面就可以完完整整看到我们写的源代码了。
并且我们进入的时候已经是暂停状态了,需要我们手动下去,这里和前端调试都大同小异了,相信这里大家都不陌生了。
除此之外,我们可以看到其他几个面板,「Console:控制台」、「Memory:内存监控」、「Profile:CPU监控」,
CPU监控
我们可以进入到「Memory面板」,点击左上角的原点表示开始监控,这个时候进行一轮例如上面的15s压测,压测结束后我们点击「stop按钮」,这个时候就可以生成这个时间段的详细数据了,结果如下:
我们也可点击hHeavy按钮切换这个数据展现形式为图表等其他方式,大家自己试试,那么从这个数据中,我们可以得到什么呢?在这其中记录了所有的调用栈,调用时间,耗时等等,我们可以详细的知道,我们代码中每一行或者每一步的花费时间,这样再对代码优化的话是完全有迹可循的,同时我们使用图表的形式也可以更为直观的查看的,当然这里不仅仅可以调试本地的,也可以通过服务器ip在设置中去调试远端服务器的,当然可能速度会相对慢一点,可以自己去尝试。同时我们也可以借助一些其他的三方包,比如「clinic」,有兴趣的各位可以自己去查看一下。
我们看他的意义是什么呢,当然是分析各个动作的耗时然后对齐进行代码优化了,接下来怎么优化呢?
代码性能优化
通过上面的分析,我们可以看到花费时间最长的是「readFileSync」,很明显是读取代码,那么我们对最最初的代码进行分析,可以看到当我们每次访问 「/indexd」路径的时候都会去重新读取文件,那么很明显这一步就是我们优化的点,我们稍加改造:
const fs = require(‘fs’)
const express = require(‘express’)
const app = express()
app.get(‘/’, (req, res) => {
res.end(‘hello world’)
})
/* 提取到外部每次程序只会读取一次 提高性能 */
const file = fs.readFileSync(__dirname + ‘/index.html’, ‘utf-8’)
app.get(‘/index’, (req, res) => {
/* return buffer */
res.end(file)
/* return stream */
// fs.createReadStream(__dirname + ‘/index.html’).pipe(res)
})
app.listen(3000)
为了直观感受,我们在改造前后分别压测一次看看,这里呢就不上图了,大家可以自己动手,会发现这样的操作可以让你的「qps」可以直接翻倍,可以看到,这样分析处出来的结果,再对代码改造可以提高非常大的效率。
同时除此之外,还有一个地方可以优化,我们发现上图我点开的箭头部分有一个「byteLengthUtf8」这样的一个步骤,可以看出他是获取我们文件的一个长度,因为我们指定了上方的获取格式是「utf-8」,那么我们想想获取长度是为了什么呢?因为「NodeJs」的底层是基于「C++」 ,最终识别的数据结构还是「buffer」,所以思路就来了,我们直接为其传递一个「buffer」是不是就更快了呢?事实确实如此,「readFileSync」不指定格式的时候默认就是「Buffer」,当我们去掉指定类型的时候,再去压测,发现「qps」再次增加了,所以在这里我们明白,在很多操作中使用「buffer」的形式可以提高代码的效率与性能。
当然还有许多其他的点,那些地方的优化可能就不太容易了,但是我们只需要去处理这些占用大头的点就已经足够了,我们只需要知道去优化的手段与思路,刚刚这个的优化就是把一些需要计算啊或者读取这种需要时间的操作移动到服务启动之前去完成就可以做到一个比较好的性能思想,那么我们性能优化需要考虑哪些点呢?
性能优化的准则
减少不必要的计算:「NodeJs」中计算会占用相当大的一部分cpu,包括一些文件的编解码等等,尽量要避免这些操作。
空间换时间:比如上面这种读取,或者一些计算,我们可以缓存起来,下次读取的时候直接调用。
掌握这两点,我们在编码过程中要尽量思考某些计算是否可以提前,尽量做到在服务启动阶段去进行处理,把在服务阶段的计算提前到启动阶段就可以做到不错的提升效果。
内存管理
垃圾回收机制
我们都知道「javascript」的内存管理都是由语言自己来做,不需要开发者来做,我们也知道其是通过「GC垃圾回收机制」实现的,我们粗略聊一下,一般来说呢,垃圾回收机制分为,新生代和老生代两部分,所有新创建的变量都会先进入新生代部分,当新生代内存区域快要分配满的时候,就会进行一次垃圾回收,把无用的变量清楚出去给新的变量使用,同时,如果一个变量在多次垃圾回收之后依然存在,那么则认为其是一个常用且不会轻易移除的变量,就会将其放入老生代区域,这样一个循环,同时,老生代区域容量更大,垃圾回收相对更慢一些。
新生代:容量小、垃圾回收更快
老生代:容量大,垃圾回收更慢
所以减少内存的使用也是提高服务性能的手段之一,如果有内存泄漏,会导致服务器性能大大降低。
内存泄漏问题处理与修复
刚刚我们上面介绍过「Memory面板」,可以检测,如何使用呢,点击面板之后点击右上角远点会产生一个快照,显示当前使用了多少内存空间,正常状态呢,我就不为大家演示了,一般如何检测呢,就是在服务启动时截取一个快照,在压测结束后再截取一个看看双方差异,你也可以在压测的过程中截取快照查看,我们先去修改一些代码制造一个内存泄漏的现场,改动如下:
const fs = require(‘fs’)
const express = require(‘express’)
const app = express()
app.get(‘/’, (req, res) => {
res.end(‘hello world’)
})
const cache = []
/* 提取到外部每次程序只会读取一次 提高性能 */
const file = fs.readFileSync(__dirname + ‘/index.html’, ‘utf-8’)
app.get(‘/index’, (req, res) => {
/* return buffer */
cache.push(file)
res.end(file)
/* return stream */
// fs.createReadStream(__dirname + ‘/index.html’).pipe(res)
})
app.listen(3000)
我们每次请求都把读取的这个文件添加到「cache」数组,那么意味着请求越多,这个数组将会越大,我们和之前一样 ,先打开调试,同时截取一份快照,然后开始压测,压测结束再截图一份,也可以在压测过程中多次截图,得到如下:
我们在压测过程中不断截取快照发现内存一直在加大,这就是很直观的可以看到内存泄漏,而且因为我们的文件不大,如果是一个更大的文件,会看起来差异更悬殊,然后我们点击「Comparsion」按钮位置,选择完快照之后进行比较,然后点击占用最大的那一列,点击之后我们就能看到详细信息了,此次泄漏就是「cache变量」所导致的,对齐进行修复即可,在我们知道如何修复和检测内存泄漏之后,我们就应该明白,减少内存的使用是提高性能的一大助力,那么我们如何减少内存的使用呢?
控制内存使用
在此之前我们聊聊「NodeJs」的「Buffer」的内存分配策略,他会分为两种情况,一种是「小于8kb」的文件,一种是「大于8kb」的文件,小于8kb的文件NodeJs认为频繁的去创建没有必要,所以每次都会先创建一个8kb的空间,然后得到空间之后的去计算「buffer」的占用空间,如果小于8kb就在8kb中给它切一部分使用,依次内推,如果遇到一个小于8kb的「buffer」使余下的空间不够使用的时候就会去开辟新的一份8kb空间,在这期间,如何有任何变量被销毁,则这个空间就会被释放,让后面的使用,这就是「NodeJs」中「Buffer」的空间分配机制,这种算法类似于一种「池」的概览。如果在我们的编码中也会遇到内存紧张的问题,那么我们也可以采取这种策略。
至此我们对于内存监控已经查找已经学会了,接下来我们来看看多进程如何使用与优化
Node多进程使用优化
现在的计算机一般呢都搭载了多核的cpu,所以我们在编程的时候可以考虑怎么去使用「多进程」或者「多线程」来尽量利用这些多核cpu来提高我们的性能。
在此之前,我们要先了解一下进程和线程的概览:
进程:拥有系统挂载运行程序的单元 拥有一些独立的资源,比如内存空间
线程:进行运算调度的单元 进程内的线程共享进程内的资源 一个进程是可以拥有多个线程的
在「NodeJs」中一般启动一个服务会有一个主线程和四个子线程,我们简单来理解其概览呢,可以把「进程」当做一个公司,「线程」当做公司的职工,职工共享公司的资源来进行工作。
在「NodeJs」中,主线程运行「v8」与「javascript」,主线程相当于公司老板负责主要流程和下发各种工作,通过「时间循环机制」 、「LibUv」再由四个子线程去进行工作。
因为「js」是一门单线程的语言,它正常情况下只能使用到一个「cpu」,不过其「子线程」在 底层也使用到了其他「cpu」,但是依然没有完全解放多核的能力,当计算任务过于繁重的时候,我们就可以也在其他的「cpu」上跑一个「javascript」的运行环境,那么我么先来看看如何用子进程来调用吧
进程的使用 child_process
我们创建两个文件,master.js和child.js,并且写入如下代码,
/* master.js */
/* 自带的子进程模块 */
const cp = require(‘child_process’)
/* fork一个地址就是启动了一个子进程 */
const child_process = cp.fork(__dirname + ‘/child.js’)
/* 通过send方法给子进程发送消息 */
child_process.send(‘主进程发这个消息给子进程’)
/* 通过 on message响应接收到子进程的消息 */
child_process.on(‘message’, (str) => {
console.log(‘主进程:接收到来自自进程的消息’, str);
})
/* chlid.js */
/* 通过on message 响应父进程传递的消息 */
process.on(‘message’, (str) => {
console.log(‘子进程, 收到消息’, str)
/* process是全局变量 通过send发送给父进程 */
process.send(‘子进程发给主进程的消息’)
})
如上,就是一个使用子进程的简单实现了,看起来和「ws」很像。每「fork」一次便可以开启一个子进程,我们可以fork多次,fork多少个合适呢,我们后边再说。
子线程 WOKer Threads
在v10版本之后,「NodeJs」也提供了子线程的能力,在官方文档中解释到,官方认为自己的事件循环机制已经做的够好足够使用了,就没必要去为开发者提供这个接口,并且在文档中写到,他可以对计算有所帮助,但是对io操作是没有任何变化的,有兴趣可以去看看这个模块,除此之外,我们可以有更简单的方式去使用多核的服务,接下来我们聊聊内置模块「cluster」
Cluster模块
在此之前我们来聊聊「NodeJs」的部署,熟悉「NodeJs」的同学应该都使用过「Pm2」,利用其可以进程提高不熟的性能,其实现原理就是基于这种模块,如果我们可以在不同的核分别去跑一个「http服务」那么是不是类似于我们后端的集群,部署多套服务呢,当客户端发送一个「Http请求」的时候进入到我们的「master node」,当我们收到请求的时候,我们把其请求发送给子进程,让子进程自己处理完之后返回给我,由主进程将其发送回去,那么这样我们是不是就可以利用服务器的多核呢?答案是肯定的,同时这些都不需要我们做过多的东西,这个模块就帮我们实现了,然后我们来实现一个这样的服务,我们创建两个文件app.js,cluster.js,第一个文件呢就是我们日常的启动文件,我们来简单的,使用我们的最开始的那个服务即可:
/* cluster.js */
const cluster = require(‘cluster’)
/* 判断如果是主线程那么就启动三个子线程 */
if(cluster.isMaster){
cluster.fork()
cluster.fork()
cluster.fork()
} else {
/* 如果是子进程就去加载启动文件 */
require(‘./index.js’)
}
就这样简单的代码就可以让我们的请求分发到不同的子进程里面去,这一点类似于负载均衡,非常简单,同时我们在启用多线程和没启动的前后分别压测,可以发现启用后的「qps」是前者的「2.5倍」拥有很大的一个提升了,也可以知道进程直接的通信是有损耗的,不然应该就是「三倍」了,那么我们要开启多少个子进程比较合适呢。我们可以使用内置模块「OS」,来获取到当前计算机的「cpu核数」的,我们加一点简单改造:
const cluster = require(‘cluster’)
const os = require(‘os’)
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数软件测试工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年软件测试全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上软件测试开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注软件测试)
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
.(img-2NoCzwy4-1712996881171)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上软件测试开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注软件测试)
[外链图片转存中…(img-KCDzIFG2-1712996881172)]
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!