从‘它怎么又挂了‘到‘服务真稳‘:我是如何用Nginx+PM2守护Node.js应用的

张开发
2026/4/18 17:54:51 15 分钟阅读

分享文章

从‘它怎么又挂了‘到‘服务真稳‘:我是如何用Nginx+PM2守护Node.js应用的
从它怎么又挂了到服务真稳我是如何用NginxPM2守护Node.js应用的凌晨三点被报警短信吵醒发现线上服务又崩了——这可能是每个独立开发者最熟悉的噩梦。Node.js应用在生产环境中的稳定性问题就像房间里的大象谁都知道存在却少有人系统解决。本文将分享一套经过实战检验的架构方案用Nginx和PM2这对黄金组合把应用的可用性从看运气变成有保障。1. 为什么你的Node.js应用总在深夜崩溃Node.js的单线程特性就像走钢丝任何一个未捕获的异常都会导致整个进程崩溃。更糟糕的是很多开发者直到上线后才发现这些问题——本地开发时用nodemon热重启掩盖了稳定性缺陷而测试环境的低并发也难暴露内存泄漏。典型崩溃场景分析未处理的Promise rejection占线上崩溃的43%同步代码阻塞事件循环如大型JSON解析内存泄漏导致OOM Killer介入第三方API超时未设置断路器去年我们一个电商项目就吃过亏促销时流量激增支付回调接口因为缺少进程守护连续崩溃导致损失数十万订单。痛定思痛后我们建立了这套防护体系# 崩溃现场常见的错误日志 Error: read ECONNRESET at TCP.onStreamRead (internal/stream_base_commons.js:209:20) FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory2. PM2不只是进程守护那么简单很多人把PM2当作简单的node app.js替代品其实它的集群模式才是真正的生产力工具。我们的配置方案包含四个关键维度2.1 基础守护配置创建ecosystem.config.js时这些参数值得特别关注module.exports { apps: [{ name: api-server, script: ./dist/index.js, instances: max, // 根据CPU核心数自动扩展 exec_mode: cluster, max_memory_restart: 1G, // 内存超过1GB自动重启 wait_ready: true, // 等待进程就绪信号 listen_timeout: 30000, kill_timeout: 5000 }] }内存控制技巧设置max_memory_restart为物理内存的70%/进程数配合--max-old-space-size调整V8堆大小使用pm2 monit实时监控内存曲线2.2 零停机部署方案传统pm2 restart all会导致服务中断我们采用分阶段滚动更新# 第一步保持一个实例始终在线 pm2 reload api-server --update-env # 第二步通过HTTP检查新版本健康状态 curl -I http://localhost:3000/healthcheck # 第三步完全切换后清理旧版本 pm2 save pm2 prune注意确保你的应用能处理SIGINT和SIGTERM信号优雅关闭数据库连接等资源2.3 监控与日志进阶玩法PM2的内置监控配合自定义指标可以构建完整的观测体系# 实时显示自定义指标 pm2 describe 0 | grep custom metrics # 日志按小时分割避免单个文件过大 pm2 install pm2-logrotate pm2 set pm2-logrotate:max_size 100M pm2 set pm2-logrotate:retain 30我们团队在PM2基础上增加了Prometheus监控关键指标包括事件循环延迟活跃句柄数集群各实例负载均衡状态3. Nginx被低估的流量指挥官很多人以为Nginx只是简单的反向代理其实它能做的远不止这些。这是我们线上环境的配置精华3.1 负载均衡智能策略upstream node_cluster { least_conn; # 最少连接数策略 server 127.0.0.1:3000 max_fails3 fail_timeout30s; server 127.0.0.1:3001 backup; # 备用实例 keepalive 32; # 保持长连接减少握手开销 } server { listen 80; server_name api.yourdomain.com; location / { proxy_pass http://node_cluster; proxy_http_version 1.1; proxy_set_header Connection ; proxy_next_upstream error timeout http_502 http_503; } }参数调优经验值keepalive_timeout建议设置为65s避开主流CDN的60s限制client_max_body_size根据业务需求调整如文件上传gzip_types加入application/json提升API响应速度3.2 熔断与限流配置当后端服务出现问题时这些配置能避免雪崩# 速率限制每秒10个请求 limit_req_zone $binary_remote_addr zoneapi_limit:10m rate10r/s; # 并发连接数限制 limit_conn_zone $binary_remote_addr zoneconn_limit:10m; location /payment { limit_req zoneapi_limit burst20 nodelay; limit_conn conn_limit 5; error_page 503 circuit_breaker; }3.3 静态资源优化技巧即使对API服务这些优化也能显著提升性能location ~* \.(js|css|png)$ { expires 365d; add_header Cache-Control public, immutable; access_log off; } # Brotli压缩需要额外模块 brotli on; brotli_types text/plain text/css application/json;4. 从监控到自愈的完整闭环有了PM2和Nginx的基础防护后我们建立了三级防御体系4.1 实时监控看板# 使用pm2-web实现web界面监控 npm install -g pm2-web pm2-web --config pm2-web-config.json关键监控指标指标类型正常范围报警阈值事件循环延迟50ms200ms持续5分钟内存使用70%总量90%持续10分钟HTTP错误率1%5%4.2 自动化恢复策略我们编写了这样的自愈脚本保存为/usr/local/bin/auto-heal.sh#!/bin/bash ERROR_COUNT$(grep -c ECONNRESET /var/log/node-app/error.log) if [ $ERROR_COUNT -gt 100 ]; then # 先尝试优雅重启 pm2 reload app-name --update-env sleep 30 # 检查是否恢复 if ! curl -Is http://localhost:3000/health | grep 200; then # 彻底重启并通知运维 pm2 restart app-name --force echo 紧急重启执行于 $(date) | mail -s 生产环境告警 adminexample.com fi fi然后添加到crontab每小时执行0 * * * * /usr/local/bin/auto-heal.sh /var/log/auto-heal.log 214.3 压力测试实战案例使用Artillery模拟真实流量验证防护体系效果config: target: https://api.example.com phases: - duration: 60 arrivalRate: 50 scenarios: - flow: - get: url: /products - post: url: /checkout json: items: [sku1, sku2]测试结果对比无防护QPS50时崩溃率38%基础PM2QPS100时崩溃率12%完整方案QPS500时崩溃率0.1%5. 那些年我们踩过的坑在实施这套方案的过程中有些经验教训值得分享TCP端口耗尽问题当并发连接数过高时可能会遇到EMFILE错误。解决方案# 增加系统文件描述符限制 echo fs.file-max 100000 /etc/sysctl.conf sysctl -p # 修改PM2启动配置 exec_interpreter: node, exec_mode: cluster, increment_var: PORT, port: 3000Nginx缓存雪崩某次大促时所有请求同时穿透缓存导致DB过载。现在我们这样配置proxy_cache_lock on; # 同一key只回源一次 proxy_cache_use_stale updating; # 更新缓存时使用旧数据PM2日志爆盘曾经因为未限制日志大小一夜之间写满磁盘。现在我们的标准配置pm2 set pm2-logrotate:max_size 100M pm2 set pm2-logrotate:retain 7 pm2 set pm2-logrotate:compress true这套方案在三个不同规模的项目中经受住了考验日活5万的教育平台连续180天无人工干预宕机跨境电商在大促期间保持99.99%可用性甚至一个IOT设备管理服务处理着每分钟上万次设备心跳检测。记住稳定性不是一次性的工作而是需要持续优化的过程——每次事故都是改进的机会。

更多文章