必知Node应用性能提升及API test 接口测试

用户注意力无法超过 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'

})

})

})

仓库

github.com/huanhunmao/...

相关推荐
王同学 学出来2 小时前
vue+nodejs项目在服务器实现docker部署
服务器·前端·vue.js·docker·node.js
一道雷2 小时前
让 Vant 弹出层适配 Uniapp Webview 返回键
前端·vue.js·前端框架
bug总结2 小时前
uniapp+动态设置顶部导航栏使用详解
java·前端·javascript
晴殇i2 小时前
深入理解MessageChannel:JS双向通信的高效解决方案
前端·javascript·程序员
毕设十刻2 小时前
基于Vue的民宿管理系统st4rf(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js
kkkAloha2 小时前
倒计时 | setInterval
前端·javascript·vue.js
源猿人2 小时前
使用 Node.js 批量下载全国行政区 GeoJSON(含省级 + 地级市)
node.js
云轩奕鹤2 小时前
智析单词书 - AI 驱动的深度英语词汇学习平台
前端·ai·产品·思维
逆光如雪2 小时前
控制台快速查看自己的log,提高开发效率
前端