用户注意力无法超过 10s

提升性能方法
使用 多个进程


可以让单线程节点应用充分使用 CPU, 每个 CPU 内核都能并行运行代码
Node 原生 cluster
javascript
// cluster.js
const cluster = require('cluster')
const os = require('os')
const WORKERS = Number(process.env.WORKERS || os.cpus().length)
if (cluster.isPrimary) {
console.log(`[master] pid=${process.pid}, workers=${WORKERS}`)
for (let i = 0; i < WORKERS; i++) cluster.fork()
// worker 异常退出自动拉起
cluster.on('exit', (worker, code, signal) => {
console.error(`[master] worker ${worker.process.pid} exit code=${code} signal=${signal}, restart...`)
cluster.fork()
})
} else {
require('./app') // 这里引入你的服务入口
}
javascript
// app.js
const http = require('http')
const server = http.createServer((req, res) => {
res.end(`hello from pid=${process.pid}\n`)
})
const PORT = Number(process.env.PORT || 3000)
server.listen(PORT, () => {
console.log(`[worker] pid=${process.pid} listening on ${PORT}`)
})
// 优雅退出(配合 reload/restart 更稳)
process.on('SIGTERM', () => {
server.close(() => process.exit(0))
})
启动
ini
node cluster.js
# 或指定进程数
WORKERS=4 node cluster.js
集群




负载均衡

最常用:Nginx upstream 负载均衡(推荐)
适合:多台机器、多容器、多实例;稳定、功能全(健康检查、限流、TLS、缓存等)。
ini
upstream api_upstream {
least_conn; # 也可用 round_robin(默认) / ip_hash(粘性)
server 127.0.0.1:3001 max_fails=3 fail_timeout=10s;
server 127.0.0.1:3002 max_fails=3 fail_timeout=10s;
server 127.0.0.1:3003 max_fails=3 fail_timeout=10s;
}
server {
listen 80;
location / {
proxy_pass http://api_upstream;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
}
}
水平扩展

PM2 (包含 集群模块)
Node 单进程吃不满多核,PM2 用 cluster 模式横向扩。
css
# 启动:按 CPU 核心数拉起多进程
pm2 start app.js -i max --exec-mode cluster --name myapp
# 或指定数量
pm2 start app.js -i 4 --exec-mode cluster --name myapp
worker thread

简易线程池 worker-pool.js
kotlin
const { Worker } = require('worker_threads')
class WorkerPool {
constructor(size, file) {
this.workers = []
this.queue = []
for (let i = 0; i < size; i++) {
const worker = new Worker(file)
worker.busy = false
worker.on('message', (result) => {
worker.busy = false
worker.resolve(result)
this.next()
})
this.workers.push(worker)
}
}
run(data) {
return new Promise((resolve) => {
this.queue.push({ data, resolve })
this.next()
})
}
next() {
const idle = this.workers.find(w => !w.busy)
if (!idle || this.queue.length === 0) return
const task = this.queue.shift()
idle.busy = true
idle.resolve = task.resolve
idle.postMessage(task.data)
}
}
module.exports = WorkerPool
worker.js(支持 postMessage)
javascript
const { parentPort } = require('worker_threads')
function heavyCalc(n) {
let sum = 0
for (let i = 0; i < n * 1e7; i++) sum += i
return sum
}
parentPort.on('message', (n) => {
parentPort.postMessage(heavyCalc(n))
})
使用线程池
javascript
const WorkerPool = require('./worker-pool')
const pool = new WorkerPool(4, './worker.js')
async function test() {
const result = await pool.run(10)
console.log(result)
}
test()
测试基础

放点 代码片段 具体 看仓库
scss
const request = require('supertest')
const app = require('../../app')
describe('Test GET /launches', () => {
test('It should respond with 200 success', async() => {
await request(app)
.get('/launches')
.expect('Content-Type', /json/)
.expect(200)
})
})
describe('Test POST /launches', () => {
const completeLaunchData = {
"mission": "USS Enterprise",
"rocket": "NCC 1701-D",
"target": "Kepler-186 f",
"launchDate": "January 4, 2028"
}
const launchDataWithoutLaunchDate = {
"mission": "USS Enterprise",
"rocket": "NCC 1701-D",
"target": "Kepler-186 f",
}
const launchInviteData = {
"mission": "USS Enterprise",
"rocket": "NCC 1701-D",
"target": "Kepler-186 f",
"launchDate": "hello"
}
test('It should respond with 201 created', async() => {
const response = await request(app)
.post('/launches')
.send(completeLaunchData)
.expect('Content-Type', /json/)
.expect(201)
const requestData = new Date(completeLaunchData.launchDate).valueOf()
const responseData = new Date(response.body.launchDate).valueOf()
expect(requestData).toBe(responseData)
})
test('It should catch missing required properties', async() => {
const response = await request(app)
.post('/launches')
.send(launchDataWithoutLaunchDate)
.expect('Content-Type', /json/)
.expect(400)
expect(response.body).toStrictEqual({
error:'Missing required launch property'
})
})
test('It should catch invalid dates', async() => {
const response = await request(app)
.post('/launches')
.send(launchInviteData)
.expect('Content-Type', /json/)
.expect(400)
expect(response.body).toStrictEqual({
error:'Invalid launch date'
})
})
})
仓库