Nodejs生产配置清单
1. 启动参数
NODE_ENV=production \
NODE_OPTIONS="--max-old-space-size=1536 --dns-result-order=ipv4first" \
UV_THREADPOOL_SIZE=16 \
node server.js
| 参数 | 含义 | 推荐值 |
|---|---|---|
--max-old-space-size | V8 老生代上限 MB | 容器 limit × 75% |
--dns-result-order=ipv4first | DNS 优先 IPv4 | 本地 IPv6 不通时必加 |
UV_THREADPOOL_SIZE | libuv 线程池大小 | 16-64(IO 密集加大) |
NODE_ENV | 环境标识 | production |
2. 容器内配置
ENV NODE_ENV=production
ENV NODE_OPTIONS="--max-old-space-size=1536"
ENV UV_THREADPOOL_SIZE=16
# DNS 配置:避免 IPv6 优先导致超时
ENV NODE_OPTIONS="--max-old-space-size=1536 --dns-result-order=ipv4first"
3. 关键环境变量
# === 应用 ===
PORT=3000
HOST=0.0.0.0 # 容器内必须 0.0.0.0
LOG_LEVEL=info
# === Node 运行时 ===
NODE_ENV=production
NODE_OPTIONS="--max-old-space-size=1536 --dns-result-order=ipv4first"
UV_THREADPOOL_SIZE=16
NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt # 自签 CA
# === 性能 ===
NODE_TLS_REJECT_UNAUTHORIZED=1 # 生产不要设 0
4. HTTP Server 配置
const server = app.listen(Number(process.env.PORT) || 3000, '0.0.0.0')
// Keep-Alive 超时(比 Nginx 大!)
server.keepAliveTimeout = 65 * 1000 // 65s > Nginx 60s
server.headersTimeout = 66 * 1000 // 比 keepAliveTimeout 大
server.requestTimeout = 30 * 1000 // 请求超时
// 最大连接
server.maxConnections = 1000
// 最大 header 大小(防大 cookie 攻击)
server.maxHeadersCount = 100
keepAliveTimeout 必须大于 Nginx/LB 的 keepalive_timeout,否则经典 502。
5. 数据库连接池
// pg 池
const { Pool } = require('pg')
const pool = new Pool({
max: 20, // 最大连接数
min: 5, // 最小保持
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000,
// 超时报错而非挂起
})
// 优雅退出时关池
process.on('SIGTERM', async () => {
await pool.end()
process.exit(0)
})
K8s 多 Pod 时总连接 = Pod 数 × max。数据库有连接上限要协调。
6. Redis 配置
const Redis = require('ioredis')
const redis = new Redis({
host: process.env.REDIS_HOST,
port: 6379,
password: process.env.REDIS_PASSWORD,
maxRetriesPerRequest: 3,
retryStrategy: (times) => Math.min(times * 50, 2000),
enableReadyCheck: true,
lazyConnect: true,
})
7. 错误处理
// 未捕获异常
process.on('uncaughtException', (err) => {
logger.fatal(err, '未捕获异常')
// 记录后安全退出(让 K8s 重启)
process.exit(1)
})
// 未处理 Promise rejection
process.on('unhandledRejection', (reason) => {
logger.error({ reason }, '未处理 rejection')
// Node 15+ 默认会退出
})
不要在 uncaughtException 里继续跑——状态可能已损坏。记日志然后退出。
8. 信号处理
let shuttingDown = false
async function shutdown(signal) {
if (shuttingDown) return
shuttingDown = true
logger.info(`收到 ${signal}`)
server.close()
await pool.end()
await redis.quit()
process.exit(0)
}
process.on('SIGTERM', () => shutdown('SIGTERM'))
process.on('SIGINT', () => shutdown('SIGINT'))
9. 安全配置
const helmet = require('helmet')
app.use(helmet()) // 安全响应头
// 限流
const rateLimit = require('express-rate-limit')
app.use('/api/', rateLimit({
windowMs: 60 * 1000,
max: 100,
standardHeaders: true,
}))
// body 限制
app.use(express.json({ limit: '1mb' }))
app.use(express.urlencoded({ limit: '1mb', extended: true }))
// CORS
const cors = require('cors')
app.use(cors({
origin: ['https://app.example.com'],
credentials: true,
}))
// trust proxy(Nginx / LB 后面必须)
app.set('trust proxy', 1)
10. 性能监控
// 暴露 metrics
const client = require('prom-client')
client.collectDefaultMetrics()
// 事件循环延迟
const lag = new client.Histogram({
name: 'nodejs_eventloop_lag_seconds',
help: 'Event loop lag',
buckets: [0.001, 0.01, 0.05, 0.1, 0.5, 1],
})
const lagInterval = setInterval(() => {
const start = process.hrtime.bigint()
setImmediate(() => {
const ms = Number(process.hrtime.bigint() - start) / 1e9
lag.observe(ms)
})
}, 1000)
lagInterval.unref()
11. 完整生产启动脚本
#!/usr/bin/env bash
set -euo pipefail
export NODE_ENV=production
export NODE_OPTIONS="--max-old-space-size=${MAX_MEMORY:-1536} --dns-result-order=ipv4first"
export UV_THREADPOOL_SIZE=${THREAD_POOL:-16}
exec node dist/server.js
Dockerfile:
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "dist/server.js"]
12. Checklist
生产 Node 应用上线前:
-
NODE_ENV=production -
--max-old-space-size< 容器 memory limit × 75% -
server.keepAliveTimeout> 代理 keepalive_timeout -
trust proxy已设 - 优雅退出实现(SIGTERM)
- 连接池有限制 + 关闭逻辑
- 日志结构化 + 级别可控
- uncaughtException / unhandledRejection 有处理
- helmet 安全头
- rate limit
- body size 限制
- 监控指标暴露