首要仍是一如既往的打个广告。假如对前端八股文感兴趣,可以看这儿,假如对react源码感兴趣,可以看这儿。感兴趣的朋友别忘了star哦,总有你要的干货[狗头]。
大纲
Node单进程形式
默认状况下,node以单进程运转,一个进程实例只要一个主线程(即事情循环线程)。关于多核CPU的核算机来说,这样做功率很低,由于只要一个核在运转,其他核都在闲置。
const express = require('express')
const app = express();
app.get('/slow', (req, res) => {
let startTime = Date.now();
while(Date.now() - startTime < 10000){}
res.send('slow request')
})
app.get('/fast', (req, res) => {
res.send('fast request')
})
app.listen(3000)
当咱们履行node index.js时,就发动了一个进程实例。这便是一个node服务。这儿只要一个进程实例。一起也是咱们的一个运用。这个进程实例包括一个主线程(也称事情循环线程)
以及N个线程池
中的线程。
只要有恳求进入咱们的服务器,就会在咱们的事情循环线程中处理。一旦恳求进入咱们的服务器,事情循环线程对其进行处理,然后回来呼应成果。
假如某个恳求处理比较耗时,很明显就会堵塞其他恳求。以上面的服务为例,咱们运用下面的代码顺次恳求/slow
以及/fast
接口:
const startTime = Date.now();
fetch('http://localhost:3000/slow').then(res => {
console.log('slow耗时:', Date.now() - startTime)
})
fetch('http://localhost:3000/fast').then(res => {
console.log('fast耗时:', Date.now() - startTime)
})
成果如下,很明显,/fast
恳求被/slow
恳求堵塞了。这是由于主线程是单线程的,由于先恳求的是slow,该恳求堵塞了10秒,在这10秒内,主线程没法处理其他恳求,导致fast恳求被堵塞了。
Cluster集群形式
可以在运用中运用cluster形式敞开多个进程实例,其中包括一个主进程和若干个worker进程。主进程负责监督worker进程实例的健康。主进程本身并不实际履行任何运用程序代码,不负责处理传入的恳求或履行其他操作,比方从数据库中读取数据。相反,主进程负责监控每个worker进程实例的运转状况。主进程可以发动单个实例,也可以中止或许重启它们,还可以向它们发送数据。这些worker进程实例负责实际处理传入的恳求,比方访问数据库,处理身份验证等。worker进程实例之间选用进程间通讯交流消息,cluster模块内置一个负载均衡器,选用Round-robin算法协调各个worker进程之间的负载。比方下图所示都是咱们的运用程序正在运转的实例。这是运转在一台核算机上的多个实例。
下面是cluster 形式的简单demo,这儿只要一个进程实例,意味着只要一个事情循环线程。
新建一个index.js文件,代码如下:
const cluster = require('cluster')
const express = require('express')
// 这个文件是否是在master形式履行的
if(cluster.isMaster){
// 主进程实例,会触发index.js以child形式从头履行一次
cluster.fork();
} else {
// worker进程实例
const app = express();
const doWork = (duration) => {
const start = Date.now();
while(Date.now() - start < duration){}
}
app.get('/', (req, res) => {
doWork(5000)
res.send('Hi there')
})
app.get('/fast', (req, res) => {
res.send('This was fast!')
})
app.listen(3000)
}
假如先恳求/,紧接着恳求/fast接口,如下图。会发现/fast恳求被堵塞了
下面的代码敞开了2个进程实例,意味着有2个事情循环线程,也便是2个独立的服务。
const cluster = require('cluster')
const express = require('express')
console.log(cluster.isMaster)
if(cluster.isMaster){
// 下面调用了2次cluster.fork函数,意味着当咱们在终端履行node index.js时,还会再额外履行2次index.js,
// 只不过别的2次的cluster.isMaster设置为false
cluster.fork();
cluster.fork();
} else {
const app = express();
const doWork = (duration) => {
const start = Date.now();
while(Date.now() - start < duration){}
}
app.get('/', (req, res) => {
doWork(5000)
res.send('Hi there')
})
app.get('/fast', (req, res) => {
res.send('This was fast!')
})
app.listen(3000)
}
如下图,可以发现/fast恳求不会被堵塞了。这是由于这儿咱们有两个进程,一个进程处理/恳求,另一个进程处理/fast恳求。
功能测验东西简介
这儿咱们运用Mac自带的ab
东西对功能进行测验。以下面的代码为例
const cluster = require('cluster')
const express = require('express')
console.log(cluster.isMaster)
if(cluster.isMaster){
cluster.fork();
} else {
const app = express();
const doWork = (duration) => {
const start = Date.now();
while(Date.now() - start < duration){}
}
app.get('/', (req, res) => {
doWork(5000)
res.send('Hi there')
})
app.get('/fast', (req, res) => {
res.send('This was fast!')
})
app.listen(3000)
}
发动服务后,运用ab
进行测验:
ab -c 50 -n 500 localhost:3000/fast
-n 500
表明总共建议500个恳求
-c 50
表明并发50个恳求,这意味着要测验一起建议50个恳求,保证在任何给定时刻点始终有50个恳求在运转并等候处理。当然,后边50个恳求在外
Requests per second
表明服务器每秒处理的恳求数。
Time per request: 23.833ms
表明平均每个恳求花费的时刻
下面表明的是恳求时刻的散布规模。比方
50% 19
表明50%的恳求在19ms内得到处理或呼应。
100% 35
表明至少有一个恳求需求35ms的时刻才干做出呼应。
Percentage of the requests served within a certain time (ms)
50% 19
66% 21
75% 22
80% 23
90% 30
95% 33
98% 35
99% 35
100% 35 (longest request)
Cluster 怎么影响服务器功能
为了便利观察Cluster Mode敞开的进程数量对功能有什么影响,下面的比方将线程池巨细调整为1。
process.env.UV_THREADPOOL_SIZE = 1;
const cluster = require('cluster')
console.log(cluster.isMaster)
if (cluster.isMaster) {
cluster.fork();
} else {
const express = require('express')
const crypto = require('crypto')
const app = express();
app.get('/', (req, res) => {
let startTime = Date.now();
console.log('承受恳求:', startTime)
crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
console.log('恳求耗时:', Date.now() - startTime)
res.send('Hi there')
})
})
app.get('/fast', (req, res) => {
res.send('This was fast!')
})
app.listen(3000)
}
发动服务,运用ab测验单个恳求耗时:
ab -c 1 -n 1 localhost:3000/
成果如下,可以发现恳求耗时559毫秒左右
有了基准值后,咱们添加恳求的数量以及并发数。这次总共建议三个恳求,并发数为2。
ab -c 2 -n 3 localhost:3000/
由于后边两个并发恳求几乎一起抵达的,但是咱们只要一个进程实例,然后这个进程实例的线程池只要一个线程,咱们的服务器一次只能处理一个pbkdf2函数的核算。
在单进程的状况下,咱们的服务是没法很好的处理并发恳求的。咱们可以测验着添加咱们的进程数量,修改咱们的demo,这次咱们运用两个进程实例:
process.env.UV_THREADPOOL_SIZE = 1;
const cluster = require('cluster')
console.log(cluster.isMaster)
if (cluster.isMaster) {
cluster.fork();
cluster.fork();
} else {
const express = require('express')
const crypto = require('crypto')
const app = express();
app.get('/', (req, res) => {
let startTime = Date.now();
console.log('承受恳求:', startTime)
crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
console.log('恳求耗时:', Date.now() - startTime)
res.send('Hi there')
})
})
app.get('/fast', (req, res) => {
res.send('This was fast!')
})
app.listen(3000)
}
相同的,咱们仍是建议总共3个恳求,并发数为2。同一时刻最多有2个恳求抵达咱们的服务器,得益于cluster的负载均衡才能,假定咱们第一个进程处理第一个恳求,第二个恳求抵达服务器时,cluser主线程发现第二个进程在闲暇状态,于是将第二个恳求转发给第二个进程处理。
从下面的成果可以看出,后边两个恳求显然是几乎并行处理的。
可以看到,当咱们添加进程实例时,形似可以进步咱们服务器的功能。由于咱们可以一个进程处理一个恳求,从而进步咱们服务器的并发才能。
Cluster 是不是进程数量越多越好
从上面的demo可以看到,咱们添加cluster进程数量,可以进步咱们服务器的并发才能。那么,是不是进程实例越多越好呢?
为了验证进程实例越多,服务器功能是不是越好,咱们看下面的demo。这次咱们创建了6个子进程。
process.env.UV_THREADPOOL_SIZE = 1;
const cluster = require('cluster')
console.log(cluster.isMaster)
if (cluster.isMaster) {
cluster.fork();
cluster.fork();
cluster.fork();
cluster.fork();
cluster.fork();
cluster.fork();
} else {
const express = require('express')
const crypto = require('crypto')
const app = express();
app.get('/', (req, res) => {
let startTime = Date.now();
console.log('承受恳求:', startTime)
crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
console.log('恳求耗时:', Date.now() - startTime)
res.send('Hi there')
})
})
app.get('/fast', (req, res) => {
res.send('This was fast!')
})
app.listen(3000)
}
下面咱们建议总共7个恳求,并发数量为6
ab -c 6 -n 7 localhost:3000/
成果如下:
实际上,这依赖于核算机CPU的装备。比方我的核算机CPU有4个内核。这意味着我的核算机处理传入恳求的才能有限。咱们这次发动的服务中有6个进程,每个进程的线程池中有1个线程,也便是服务器最多只能一起处理6个恳求。这儿咱们建议7个恳求,并发数为6,意味着同一时刻最多有6个恳求抵达咱们的服务器。刚好可以被咱们的6个进程实例处理,每个进程的线程池又只要一个线程,因而相当于每个线程池处理1个恳求,那便是有6个线程等候CPU调度,4个内核轮流履行这6个线程,很显然,每个内核平均履行1.5个线程。假如一个pbkdf2耗时550毫秒,那么平均每个内核需求履行550 * 1.5=825毫秒,也便是一个恳求需求耗时825毫秒左右。这仍是抱负状况,没考虑CPU在进程之间切换的时刻开支。这也是为啥咱们从控制台的输出得到850毫秒左右的恳求耗时。
因而,尽管咱们可以一起处理一切这些传入的恳求,但终究成果是咱们的全体功能受到影响。由于咱们的CPU需求一起调度处理这6个恳求。
这次,咱们只创建4个进程实例,数量坚持和咱们核算机CPU内核数一样。
process.env.UV_THREADPOOL_SIZE = 1;
const cluster = require('cluster')
console.log(cluster.isMaster)
if (cluster.isMaster) {
cluster.fork();
cluster.fork();
cluster.fork();
cluster.fork();
} else {
const express = require('express')
const crypto = require('crypto')
const app = express();
app.get('/', (req, res) => {
let startTime = Date.now();
console.log('承受恳求:', startTime)
crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
console.log('恳求耗时:', Date.now() - startTime)
res.send('Hi there')
})
})
app.get('/fast', (req, res) => {
res.send('This was fast!')
})
app.listen(3000)
}
从下图可以看出,最快的恳求只需求555毫秒即可处理完结。而最慢的恳求需求1135毫秒处理完结。因而,尽管咱们运用了更少的进程,但实际上咱们终究的功能是变得更好的。
因而,通过大幅添加运用程序内部的子进程数量,使其超出核算机CPU的内核数量,将对咱们的服务功能发生净负面的影响。
运用PM2敞开cluster mode
一般在生产环境中,咱们并不直接运用 nodejs 的cluster模块敞开多进程实例,而是运用pm2。pm2提供了进程看护,比方进程实例挂了主动重启等才能。
一般在开发环境不运用pm2
新建index.js文件:
const express = require('express')
const crypto = require('crypto')
const app = express();
app.get('/', (req, res) => {
let startTime = Date.now();
console.log('承受恳求:', startTime)
crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
console.log('恳求耗时:', Date.now() - startTime)
res.send('Hi there')
})
})
app.get('/fast', (req, res) => {
res.send('This was fast!')
})
app.listen(3000)
发动服务
pm2 start index.js -i 0
中止服务
pm2 delete index
罗列一切进程
pm2 list
显示进程信息
pm2 show index
进程监控
pm2 monit