在前面的小节中,我们提到利用 cluster 模块或 PM2 的集群模式可以在单台服务器上充分利用多核 CPU。但是,当流量继续增长,需要通过多台服务器来分担压力时,就需要在 Node.js 应用实例之前引入专门的负载均衡器。Nginx 是目前使用最广泛的反向代理和负载均衡服务器,它性能极高、配置灵活,还能承担静态资源服务、HTTPS 终止、缓存等职责。
为什么需要 Nginx 在前置
Node.js 进程本身可以直接监听 80 或 443 端口对外提供服务,但引入 Nginx 之后可以获得几个关键的增强能力:
- 负载均衡:将流量按照一定策略分发给后端的多个 Node.js 实例,可以是同一台机器的不同进程,也可以是多台不同服务器。
- 零宕机部署:重启某一个 Node.js 实例时,其他实例仍可继续服务,Nginx 能够自动摘除故障节点,保证服务整体可用。
- 静态资源加速:Nginx 处理静态文件的能力远优于 Node.js,可以直接将由 Express/Koa 等服务传递过来的请求中静态资源分离出来,降低 Node 进程的负载。
- HTTPS 卸载:在 Nginx 上统一配置 SSL 证书并终止 HTTPS,后端的 Node.js 实例只需处理 HTTP 请求,简化证书管理和应用更新。
- 请求缓存与压缩:Nginx 可以缓存反向代理的响应,对返回的内容进行 Gzip 压缩,进一步优化传输效率。
配置一个基本的反向代理
假设我们有两个 Node.js 应用实例分别运行在本地 3001 和 3002 端口,现在希望用 Nginx 监听 80 端口并将请求轮询分发到这两个实例。
首先,在 /etc/nginx/conf.d/node_app.conf 或站点配置文件中添加:
upstream node_backend {
server 127.0.0.1:3001;
server 127.0.0.1:3002;
}
server {
listen 80;
server_name example.com;
# 日志记录
access_log /var/log/nginx/node_app_access.log;
error_log /var/log/nginx/node_app_error.log;
# 静态资源直接由 Nginx 处理
location /static/ {
root /var/www/node_app/public;
expires 30d;
add_header Cache-Control "public, immutable";
}
# 其他请求转发到 Node 集群
location / {
proxy_pass http://node_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
这一配置的核心点:
upstream块定义了一组后端服务器,Nginx 会在其中进行负载均衡。proxy_pass http://node_backend将请求转发给这个上游组。proxy_set_header保证了真实客户端 IP、协议等信息能传递到 Node.js 应用,这在读取req.ip或日志记录时很重要。/static/路径直接由 Nginx 读取本地磁盘文件,并设置了长期的缓存头,不再拖累 Node 进程。
负载均衡策略
Nginx 的 upstream 支持多种负载均衡算法,可以根据业务需要选择:
轮询(默认)
upstream node_backend {
server 127.0.0.1:3001;
server 127.0.0.1:3002;
}
请求依次轮流分配到每个服务器,如果后端实例性能相近,这是最简单的方案。
权重轮询
upstream node_backend {
server 127.0.0.1:3001 weight=3;
server 127.0.0.1:3002 weight=1;
}
在服务器配置不均衡(如一台机器比另一台强)时,通过 weight 让高性能实例承担更多请求。
最少连接(least_conn)
upstream node_backend {
least_conn;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
}
将请求分配到当前活跃连接数最少的服务器,对于长连接类应用(如 WebSocket)效果较好,能更真实地反映负载情况。
IP Hash
upstream node_backend {
ip_hash;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
}
根据客户端 IP 计算哈希值,使得同一客户端的请求始终落到同一台后端,用于解决 Session 黏连问题。但在现代分布式系统中更推荐使用外部 Session 存储(Redis)来避免服务器绑定。
支持 WebSocket 反向代理
Node.js 应用常涉及 WebSocket 实时通信(如 Socket.IO),Nginx 需要正确升级连接协议。上文的配置中已经包含了关键头部:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
这三行确保了 WebSocket 握手时 Upgrade 和 Connection 头部能被传递到后端,从而成功建立长连接。如果缺少这些配置,WebSocket 连接会被降级为普通的 HTTP 长轮询,极大降低性能。
健康检查与故障转移
Nginx 默认提供简单的被动健康检查:如果向某台服务器转发请求失败(如连接拒绝或超时),该服务器将被标记为不可用,并在一定时间后重试。可以通过以下参数调整行为:
upstream node_backend {
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3002 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3003 backup;
}
max_fails等于 3 表示在fail_timeout时间内出现 3 次失败后暂时标记为不可用。backup表示只有当所有主服务器都不可用时,请求才会转发到这台备份服务器。
对于高可用要求更严的场景,可以结合 nginx_upstream_check_module 插件实现主动健康检查(对后端发送周期性探测请求),但大部分中小规模项目使用默认的被动检查已足够。
与 PM2 集群模式的配合
当使用 PM2 的集群模式启动多个 Node.js 实例时,每个实例会占用一个独立端口(或通过 cluster 共享端口)。如果 Nginx 负责对外服务,我们通常会让 PM2 的每个实例监听不同的端口,例如 3001、3002…,然后在 Nginx upstream 中一一列出。生产环境中,建议将 PM2 的实例绑定到本地回环地址(127.0.0.1),避免暴露在公网。
一个自动化管理的方法是用服务发现工具动态更新 Nginx 的 upstream 配置,但对于静态实例数量,手动维护配置已经足够稳定。
生产配置建议
真实项目中部署 Nginx 时还需要注意以下几点:
- 使用
proxy_set_header传递原始客户端信息:Node.js 的Trust Proxy设置需要开启(如在 Express 中使用app.set('trust proxy', true)),才能正确识别从 Nginx 传来的X-Forwarded-*头部。 - 开启 Gzip 压缩:在 Nginx 上全局开启 Gzip,可以将文本型响应(JSON、HTML)体积压缩到原来的 1/3,节省带宽并加快传输。
- 限制请求速率:利用
limit_req和limit_conn模块保护后端 Node 进程,避免单 IP 恶意请求打满资源。 - 长连接优化:在
proxy_pass时调整proxy_read_timeout和proxy_send_timeout以适应业务最长的请求时间,避免正常慢请求被误杀。 - 日志轮转:配置
access_log的滚动策略,防止日志文件撑满磁盘。
示例:一个完整的 HTTPS 负载均衡配置
假定你已经拥有了 SSL 证书,以下配置展示了如何同时启用 HTTPS、静态资源服务和 WebSocket 代理:
upstream node_app {
least_conn;
server 127.0.0.1:3001 weight=2;
server 127.0.0.1:3002 weight=1;
server 127.0.0.1:3003 backup;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/ssl/certs/example.com.pem;
ssl_certificate_key /etc/ssl/private/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# 静态文件
location /static/ {
root /var/www/app/public;
expires 7d;
}
# API 接口和 WebSocket
location / {
proxy_pass http://node_app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
}
}
# HTTP 自动跳转到 HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
当你在多台服务器上进行水平扩展时,可以将 upstream 中的 IP 地址替换为其他机器的内网地址,Nginx 就会将请求均衡分发到整个 Node.js 集群上。
小结
Nginx 反向代理与负载均衡是 Node.js 生产环境高可用架构中不可或缺的一环。它充当了流量的“守门人”,将客户端的海量连接简单高效地分发到多个 Node.js 进程,同时卸下了 HTTPS、静态资源、压缩等额外负担。结合 PM2 或 cluster,开发者可以搭建出一个轻量但健壮的 Node.js 服务集群,足以应对绝大多数互联网应用的性能要求。