Node-3
crypto 模块
crypto
模块提供了加密功能,实现了包括对 OpenSSL
的哈希、HMAC
、加密、解密、签名、以及验证功能的一整套封装。
util 模块
util
是 node
的一个核心模块,提供常用函数的集合
const { promisify } = require('util')
promisify
可以将一个满足 【最后一个形参是回调函数,该回调函数的第一个参数是错误信息error
】的函数包装成 promise
异步函数,如fs.readFile
// download-git-repo 下载并解压一个 git 库(GitHub、GitLab、Bitbucket)
// ora 用于显示加载中的效果,类似前端页面的loading
const download = require('download-git-repo'), ora = require('ora')
const process = ora('下载项目...')
process.start()
// github:daybrush/moveable
download('github:daybrush/moveable', './download', err => {
if(err) {
process.fail()
} else {
process.succeed()
}
})
// 改造 ==>
clone('github:daybrush/moveable', './download')
async function clone(repo, desc) {
const { promisify } = require('util')
const download = promisify(require('download-git-repo'))
const ora = require('ora')
const process = ora('下载项目...')
process.start()
try {
await download(repo, desc)
process.succeed()
} catch (error) {
process.fail()
}
}
readline 模块
readline
是Node
的一个核心模块,通过命令行接口方式控制输入和输出;用于从可读流如process.stdin
读取数据,每次读取一行。
const readline = require('readline')
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
rl.on('line', input => {
// input 是用户输入的内容
if(input === 'quit') {
// 关闭命令行交互
rl.close()
} else {
console.log('input: ', input)
}
})
rl.on('close', () => {
// 收到关闭交互事件,则退出程序
process.exit(0)
})
cluster 模块
单个 Node
实例运行在单个线程中,为了充分利用多核系统,需要启用一组 Node
进程去处理负载任务。
Node
提供了 cluster
核心库,可以创建共享服务器端口的子进程,充分利用多核CPU
。
-
Node
应用程序- app.js
const Koa = require('koa') const app = new Koa() //... if (!module.parent) { app.listen(3000) } else { module.exports = app }
-
cluster
集群- cluster.js
const cluster = require('cluster') //查看CPU的数量 const numCPUs = require('os').cpus().length if(cluster.isMaster) { // 主进程 // 启动子进程 --> 多核利用 for(let i = 0; i < numCPUs; i++) { const worker = cluster.fork() // worker.id 进程的唯一编号,在 cluster.workers 中的索引 // worker.process.pid 进程的PID } cluster.on('exit', worker => { // 退出一个,启动一个 --> 故障恢复 const worker = cluster.fork() }); // 当进程终止,会接收到一个信号,则杀死所有进程 // process.on('SIGTERM', () => { // for(let pid in workers) { // 根据进程PID杀死进程 // process.kill(pid) // } // 退出主进程 // process.exit(0) //}) } else { // 工作进程 const app = require('./app') app.use(async (ctx, next) => { console.log('当前 worker: ', cluster.worker.id) next() }) app.listen(3000) }
-
cluster
创建的进程有两种,父(主)进程和子(工作)进程,父进程只有一个,子进程有多个,一般为了充分利用服务器资源,创建的子进程个数与CPU核数相等; - 每次执行
cluster.fork()
都会重新执行当前文件,不同的是cluster.isMaster
的值为false
。为了充分利用多核资源,几核CPU
就会启动几个进程,形成集群,高可用的Node
环境!
-
cluster
的实现原理:cluster
其实是借助child_process
模块的fork()
来创建子进程的,通过fork
方式创建的子进程与父进程之间建立了IPC
通道,且支持双向通信,但子进程与子进程之间无法直接通信。
端口共享原理
cluster
开启的工作进程(子进程)监听的都是 3000 端口,并不会冲突。其实是一种假象。
原理: 父(主)进程负责监听端口并接收请求,然后分发给子进程,由子进程负责处理。
子进程虽然调用了
listen()
方法试图监听端口,但在net.js
源码中,listen()
方法通过listenInCluster
方法来区分父进程与子进程,子进程调用的listen()
方法其实会发送给父进程queryServer message
,父进程会检测是否创建了TCP Server
,如果没有,则创建并绑定端口。并且,父进程会记录向自己发送消息的子进程,方便后续分发用户请求。
负载均衡的实现
负载均衡直接依赖 cluster
的请求调度策略,在v6.0
版本之前,cluster
的请求策略依赖于操作系统,理论上来说性能最好,但实际上在请求调度方面并不太均匀,可能出现 8
个子进程中只有 2
到 3
个处理了70%
的连接请求。因此,v6.0
版本中增加了cluster.SCHED_RR(round-robin)
,目前已成为默认的调度策略(windows
环境除外)。
可以通过设置 NODE_CLUSTER_SCHED_POLICY
环境变量来修改调度策略
NODE_CLUSTER_SCHED_POLICY='rr'
NODE_CLUSTER_SCHED_POLICY='none'
亦或者设置cluster
的schedulingPolicy
属性:
cluster.schedulingPolicy = cluster.SCHED_NONE
cluster.schedulingPolicy = cluster.SCHED_RR
Node
实现 round-robin
-
Node
内部维护两个队列:-
free
队列记录当前可用的worker
-
handles
队列记录需要处理的TCP
请求
-
- 当新请求到达时,父进程将请求暂存
handles
队列,从free
队列中出队一个worker
,进入worker
处理(handoff
)阶段,关键逻辑实现如下:RoundRobinHandle.prototype.distribute = function(err, handle) { this.handles.push(handle); const worker = this.free.shift(); if (worker) { this.handoff(worker); } };
-
worker
处理阶段首先从handles
队列出队一个请求,然后通过进程通信的方式通知子worker
进行请求处理,当worker
接收到通信消息后发送ack
信息,继续响应handles
队列中的请求任务,当worker
无法接受请求时,父进程负责重新调度worker
进行处理。关键逻辑如下:RoundRobinHandle.prototype.handoff = function(worker) { const handle = this.handles.shift(); if (handle === undefined) { this.free.push(worker); // Add to ready queue again. return; } const message = { act: 'newconn', key: this.key }; sendHelper(worker.process, message, handle, (reply) => { if (reply.accepted) handle.close(); else // Worker is shutting down. Send to another. this.distribute(0, handle); this.handoff(worker); }); };