nginx —— 反向代理、负载均衡与排障

运维工具手册 12-隧道与高可用 8.3 KB 原文

nginx —— 反向代理、负载均衡与排障

分类:12 隧道与高可用 适用:Nginx 1.18 ~ 1.27(开源版;部分指令为 Plus 专有) 一句话:七层入口的标准件,做反向代理、负载均衡、TLS 卸载、限流与灰度。

1. 它解决什么问题

  • 一个入口后面挂多个后端,做负载与故障转移。
  • 统一处理 TLS、gzip、跨域、限流。
  • 502/504 频发,要定位是网关还是后端。
  • 按路径/域名/Header 分流(灰度、多租户)。

2. 配置结构与校验

/etc/nginx/
├── nginx.conf           # 主配置
├── conf.d/*.conf        # 站点配置
├── sites-enabled/*      # Debian 系
├── mime.types
└── snippets/
nginx -t                       # 语法检查(改完必做)
nginx -T                       # 打印合并后的完整配置
nginx -s reload                # 平滑重载
nginx -s quit                  # 优雅退出
nginx -s stop                  # 快速停止
nginx -v / -V                  # 版本 / 编译参数

任何配置变更:nginx -t 通过后再 reload。reload 不会断开现有连接。

3. 最小反向代理

upstream app_backend {
    server 10.0.0.11:8080 weight=5 max_fails=3 fail_timeout=10s;
    server 10.0.0.12:8080 weight=5 max_fails=3 fail_timeout=10s;
    keepalive 64;                     # 与后端保持长连接
    keepalive_requests 1000;
    keepalive_timeout 60s;
}

server {
    listen 80;
    server_name app.example.com;

    access_log /var/log/nginx/app.access.log main;
    error_log  /var/log/nginx/app.error.log warn;

    client_max_body_size 20m;

    location / {
        proxy_pass http://app_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        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_connect_timeout 5s;
        proxy_send_timeout    60s;
        proxy_read_timeout    60s;
        proxy_next_upstream error timeout http_502 http_503;
    }
}

proxy_pass 的斜杠陷阱

location /api/ {
    proxy_pass http://backend;        # 保留 /api/ → 后端收到 /api/xxx
}
location /api/ {
    proxy_pass http://backend/;       # 去掉 /api/ → 后端收到 /xxx
}

尾部有无 / 决定是否截断 location 前缀,这是最高频的配置事故。

4. location 匹配优先级

= 精确      → 最高
^~ 前缀     → 次高(匹配后不再查正则)
~ 正则      → 按出现顺序
~* 正则忽略大小写
无修饰前缀  → 最低(最长匹配)
location = /health { return 200 "ok\n"; }
location ^~ /static/ { root /data; }
location ~* \.(jpg|png|css|js)$ { expires 30d; }
location / { proxy_pass http://app_backend; }

= /health 放在最前,避免被 location / 拦截。

5. 日志格式(排障基础)

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                '$status $body_bytes_sent "$http_referer" "$http_user_agent" '
                'rt=$request_time urt=$upstream_response_time '
                'ua=$upstream_addr us=$upstream_status '
                'rid=$request_id';

access_log /var/log/nginx/access.log main;
变量 含义
$request_time Nginx 从收到请求到发送完响应的总时间
$upstream_response_time 后端处理时间
$upstream_addr 实际转发的后端
$upstream_status 后端返回状态码
$request_id 请求唯一 ID(贯穿日志)

判读:

  • rt 大、urt 小 → 慢在客户端或网络回传。
  • rt ≈ urt 且都大 → 后端慢。
  • urt 为 - → 请求没到后端(Nginx 自己返回,如 499、413)。

6. 限流与限连接

# 定义在 http 块
limit_req_zone  $binary_remote_addr zone=perip:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=conn_perip:10m;

server {
    location /api/ {
        limit_req  zone=perip burst=20 nodelay;
        limit_conn conn_perip 20;
        limit_req_status 429;
        limit_conn_status 429;
        proxy_pass http://app_backend;
    }
}
参数 含义
rate=10r/s 每秒 10 个请求
burst=20 允许突发 20 个排队
nodelay 突发请求立即处理(否则排队变慢)

zone 的 10m 约存 16 万个 IP 状态。

7. TLS 配置

server {
    listen 443 ssl;
    http2 on;                          # 1.25.1+ 写法;老版本用 listen 443 ssl http2
    server_name app.example.com;

    ssl_certificate     /etc/nginx/ssl/fullchain.pem;   # 必须含中间证书
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305;
    ssl_prefer_server_ciphers off;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/nginx/ssl/chain.pem;

    add_header Strict-Transport-Security "max-age=31536000" always;
}

HTTP 跳 HTTPS:

server {
    listen 80;
    server_name app.example.com;
    return 301 https://$host$request_uri;
}

8. 502 / 504 排查流程

502 Bad Gateway

含义:Nginx 连不上后端,或后端返回了非法响应。

# 1. 看错误日志(关键)
tail -100 /var/log/nginx/error.log
# 典型:connect() failed (111: Connection refused) while connecting to upstream
#      upstream prematurely closed connection

# 2. 从 Nginx 机器直连后端
curl -v http://10.0.0.11:8080/health
nc -zv 10.0.0.11 8080

# 3. 后端是否在跑、是否只监听 127.0.0.1
ss -lntp | grep 8080

# 4. 看后端日志(崩溃/超时/重启)
journalctl -u app -n 100

# 5. SELinux / 防火墙
getenforce; firewall-cmd --list-all

常见原因:后端挂了、端口错、只绑 127.0.0.1、client_max_body_size 太小(413 不是 502)、后端主动断连、PHP-FPM 进程不够。

504 Gateway Timeout

含义:后端在超时时间内没返回。

grep 'upstream timed out' /var/log/nginx/error.log | tail
proxy_connect_timeout 5s;
proxy_send_timeout    60s;
proxy_read_timeout    300s;      # 长任务按需放大

但先确认后端为什么慢:$upstream_response_time、后端 APM、数据库慢查询。盲目调大超时只会把问题藏起来。

499 Client Closed Request

客户端主动断开(用户关页面、上游超时)。大量 499 通常是响应太慢导致客户端放弃。

9. 实战场景

9.1 灰度发布(按 Header / Cookie)

upstream app_v1 { server 10.0.0.11:8080; }
upstream app_v2 { server 10.0.0.12:8080; }

map $http_x_canary $backend_pool {
    default  app_v1;
    "true"   app_v2;
}

server {
    location / {
        proxy_pass http://$backend_pool;     # 注意:变量方式不做健康检查
        proxy_set_header Host $host;
    }
}

proxy_pass 用变量时 Nginx 会在运行时解析,失去 upstream 的健康检查与连接池,生产更推荐用 split_clients 或按权重调整。

9.2 按权重分流

upstream app_backend {
    server 10.0.0.11:8080 weight=9;   # 90%
    server 10.0.0.12:8080 weight=1;   # 10%
}

9.3 静态资源与后端分离

location /static/ {
    alias /data/static/;
    expires 30d;
    add_header Cache-Control "public, immutable";
    access_log off;
}

location / {
    proxy_pass http://app_backend;
}

root 会拼接完整 URI,alias 会替换 location 部分,注意区分。

9.4 开启 stub_status 监控

location /nginx_status {
    stub_status;
    allow 127.0.0.1;
    deny all;
}
Active connections: 291
server accepts handled requests
 16630948 16630948 31070465
Reading: 6 Writing: 179 Waiting: 106

Waiting 高说明 keepalive 空闲连接多;Reading+Writing 高说明并发处理中。

9.5 大文件上传 413

client_max_body_size 100m;
proxy_request_buffering off;        # 大文件直传后端,避免磁盘缓冲

9.6 查看生效配置

nginx -T | grep -A10 'server_name app.example.com'

排查"改了不生效":确认改的是被 include 的文件、nginx -t 是否通过、是否 reload、是否有更高优先级的 server 块。

10. 常见坑

  1. proxy_pass 尾部斜杠:路径被截断或重复,见第 3 节。
  2. Host 头没传:后端生成错误的绝对 URL、防盗链失效。
  3. 没配 X-Forwarded-For:后端拿到的都是 Nginx 的 IP,日志与限流失效。
  4. keepalive 配了但没生效:必须同时 proxy_http_version 1.1 + proxy_set_header Connection ""。
  5. client_max_body_size 默认 1m:上传大文件报 413。
  6. location 优先级误判:正则 location 会抢先于普通前缀,用 ^~ 明确。
  7. root 与 alias 混用:路径拼错,404 且日志看不出原因。
  8. reload 后旧 worker 不退出:长连接未断,worker_shutdown_timeout 控制。
  9. worker_connections 不足:报 worker_connections are not enough,同时要调 worker_rlimit_nofile。
  10. upstream 健康检查是"被动"的:开源版没有主动健康检查,靠 max_fails/fail_timeout,恢复慢。
  11. proxy_buffering 与 SSE/流式:需要 proxy_buffering off 才能实时输出。
  12. 证书链不全:浏览器正常但 Java 客户端报错,ssl_certificate 要用 fullchain。
  13. 日志没切:access.log 涨满磁盘,配 logrotate 并 nginx -s reopen。
  14. 配置改错导致 reload 失败:旧配置仍在运行,容易误以为已生效,务必看 nginx -t 输出。

11. 速查表

nginx -t / -T
nginx -s reload / quit / stop / reopen
ps -ef | grep nginx
ss -lntp | grep nginx

# 排障
tail -f /var/log/nginx/error.log
awk '{print $9}' access.log | sort | uniq -c | sort -nr
grep 'upstream timed out' error.log | tail
grep -c ' 502 ' access.log

# 关键指令
proxy_pass http://backend;          # 保留前缀
proxy_pass http://backend/;         # 去掉前缀
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;
proxy_set_header Connection "";
proxy_read_timeout 60s;
client_max_body_size 20m;

limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
limit_req zone=perip burst=20 nodelay;

upstream x { server ip:port weight=5 max_fails=3 fail_timeout=10s; keepalive 64; }
nginx —— 反向代理、负载均衡与排障 - 知识库 - 起航社区