Skip to content

Nginx 负载均衡配置与 upstream 模块

凌晨 3 点,你被报警电话吵醒:「下单接口响应时间从 50ms 飙升到 8 秒」。

你登录服务器一看,后端 Tomcat 的 CPU 使用率只有 30%,内存也很充裕。

问题不在后端应用——是 Nginx 的连接队列满了。

这不是故事,是真实的事故。Nginx 配置不当,再好的后端服务器也发挥不出实力。

upstream 模块基础

upstream 是 Nginx 的后端服务器分组模块,负责定义一组后端服务器供 proxy_pass 使用。

最简配置

nginx
http {
    upstream backend {
        server 192.168.1.10:8080;
        server 192.168.1.11:8080;
        server 192.168.1.12:8080;
    }

    server {
        listen 80;
        location / {
            proxy_pass http://backend;
        }
    }
}

这个配置会让请求轮询分发给三台后端服务器。

负载均衡算法配置

轮询(默认)

nginx
upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

加权轮询

nginx
upstream backend {
    server 192.168.1.10:8080 weight=5;   # 权重 5
    server 192.168.1.11:8080 weight=2;   # 权重 2
    server 192.168.1.12:8080 down;        # 暂时下线
}

IP 哈希(会话保持)

nginx
upstream backend {
    ip_hash;  # 基于客户端 IP 做哈希
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

最少连接

nginx
upstream backend {
    least_conn;  # 动态选择连接数最少的服务器
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

随机(两阶段)

nginx
upstream backend {
    random two;  # 先随机选两台,再从中选一台
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

服务器状态标记

nginx
upstream backend {
    server 192.168.1.10:8080;                    # 正常
    server 192.168.1.11:8080 down;                # 永久下线
    server 192.168.1.12:8080 backup;              # 备用服务器
    server 192.168.1.13:8080 max_fails=3;         # 失败 3 次后摘除
    server 192.168.1.14:8080 fail_timeout=30s;    # 30s 后重新尝试
    server 192.168.1.15:8080 max_fails=2 fail_timeout=60s;
}

状态说明

状态含义
down永久摘除,不参与负载均衡
backup备用服务器,所有正常服务器都挂了才启用
max_fails最大失败次数,超过后摘除
fail_timeout失败后多长时间内不参与负载均衡

Keepalived 长连接

Nginx 默认行为是每发完一个请求就关闭连接。对于后端是 Java 服务(需要建立 TCP 连接),频繁建连开销很大。

开启 keepalive 可以复用连接:

nginx
upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;

    # 启用长连接
    keepalive 32;        # 保持 32 个空闲连接
    keepalive_requests 1000;  # 每个连接最多处理 1000 个请求
    keepalive_timeout 60s;    # 空闲连接保持 60 秒
}

server {
    location / {
        proxy_pass http://backend;
        # 启用 HTTP/1.1(必须)
        proxy_http_version 1.1;
        # 清空连接头(必须)
        proxy_set_header Connection "";
    }
}

keepalive 配置建议

后端服务器数量keepalive 连接数说明
332-64每台服务器保持 10-20 个连接
1064-128每台服务器保持 6-12 个连接
50+128-256配合 upstream 动态发现

粘性会话(Sticky Sessions)

除了 ip_hash,Nginx Plus 和某些第三方模块支持基于 Cookie 的会话保持:

nginx
upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;

    # 基于 Cookie 的会话保持
    sticky cookie srv_id expires=1h domain=.example.com path=/;
}
策略原理优缺点
cookie服务器设置 Cookie精确但依赖客户端支持
routeURI 参数携带路由信息侵入性强,不推荐
learn学习请求特征占用内存,不推荐

连接数和超时配置

nginx
upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;

    # 连接池配置
    keepalive 32;
    keepalive_timeout 60s;

    # 失败重试配置
    proxy_next_upstream error timeout http_502;
    proxy_next_upstream_tries 3;
    proxy_next_upstream_timeout 10s;
}

常见错误码重试

nginx
# 502: Bad Gateway - 后端服务崩溃
# 503: Service Unavailable - 后端服务不可用
# 504: Gateway Timeout - 后端响应超时
# error: 连接错误、读取错误
# timeout: 超时

proxy_next_upstream error timeout http_502 http_503 http_504;

健康检查配置

Nginx Plus 支持主动健康检查,开源版本需要借助第三方模块或外部工具:

Nginx Plus 主动检查

nginx
upstream backend {
    zone backend 64k;

    server 192.168.1.10:8080;
    server 192.168.1.11:8080;

    # 健康检查
    health_check interval=5s fails=3 passes=2 uri=/health;
}

开源版本方案

使用 nginx_upstream_check_module 第三方模块:

nginx
upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    check interval=3000 rise=2 fall=3 timeout=1000 type=http;
    check_http_send "HEAD /health HTTP/1.0\r\n\r\n";
    check_http_expect_alive http_2xx http_3xx;
}

分域名路由

nginx
upstream api_backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

upstream admin_backend {
    server 192.168.1.20:8080;
    server 192.168.1.21:8080;
}

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

    location / {
        proxy_pass http://api_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

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

    location / {
        proxy_pass http://admin_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

分路径路由

nginx
upstream static_backend {
    server 192.168.1.10:80;
}

upstream api_backend {
    server 192.168.1.20:8080;
    server 192.168.1.21:8080;
}

upstream file_backend {
    server 192.168.1.30:8080;
    server 192.168.1.31:8080;
}

server {
    listen 80;

    # 静态资源
    location /static/ {
        proxy_pass http://static_backend;
        proxy_cache_valid 200 1d;
    }

    # API 请求
    location /api/ {
        proxy_pass http://api_backend;
        proxy_connect_timeout 5s;
        proxy_read_timeout 30s;
    }

    # 文件上传
    location /upload {
        proxy_pass http://file_backend;
        client_max_body_size 100m;
    }

    # 默认
    location / {
        proxy_pass http://api_backend;
    }
}

实战配置模板

nginx
user nginx;
worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 10240;
    use epoll;
    multi_accept on;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    # 性能优化
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    # 日志格式
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for" '
                    'upstream: $upstream_addr upstream_status: $upstream_status';

    # 开启 gzip
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml application/json application/javascript;

    # 限流
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;
    limit_conn_zone $binary_remote_addr zone=addr_limit:10m;

    upstream backend {
        server 192.168.1.10:8080 weight=5;
        server 192.168.1.11:8080 weight=3;
        server 192.168.1.12:8080 backup;

        keepalive 32;
        keepalive_timeout 60s;

        # 失败重试
        proxy_next_upstream error timeout http_502 http_503;
    }

    server {
        listen 80;
        server_name example.com;

        # 限流
        limit_req zone=api_limit burst=200 nodelay;
        limit_conn addr_limit 50;

        # 请求日志
        access_log /var/log/nginx/access.log main;

        location / {
            proxy_pass http://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_buffering on;
            proxy_buffer_size 4k;
            proxy_buffers 8 4k;
            proxy_busy_buffers_size 8k;
        }

        location /health {
            proxy_pass http://backend;
            access_log off;
        }
    }
}

思考题:

假设你的 Nginx upstream 配置了 3 台后端服务器,某天其中一台机器的网卡被打满,网络延迟从 1ms 飙升到 500ms。

问题:

  1. 在默认的轮询策略下,1/3 的请求会打到这台「卡机」,用户体验会明显下降吗?
  2. 如果换成最小连接数策略,Nginx 能感知到这台机器的延迟吗?
  3. 你有什么办法让 Nginx 更智能地处理这种情况?

提示:考虑 TCP 连接建立时间、upstream 的 timeout 配置、以及 Nginx 的连接队列。

基于 VitePress 构建