【架构实战】Nginx七层负载均衡:从配置到原理,从入门到精通

一、我与Nginx的"相爱相杀"

作为一名后端开发,我第一次接触Nginx是在实习的时候。那时候我以为Nginx就是一个"配置起来很简单"的文件服务器,用来放静态页面和做反向代理。

后来真正用了才发现,Nginx的水深着呢:

  • upstream里配置错误,排查了半天发现是IP写错了
  • 限流配置不起作用,原来是放在了错误的location里
  • 长连接配置不对,导致大量连接处于TIME_WAIT状态
  • 缓存配置了一直没生效,原来需要开启proxy_cache_path
  • 配置reload后请求成功率下降,找了半天发现是worker进程数设置有问题

这些坑一个一个踩过来,我才算真正理解了Nginx。今天把我这些年积累的Nginx经验整理成文,希望能帮你少走弯路。


二、Nginx基础架构:先理解它是怎么工作的

2.1 Nginx的工作模型

Nginx采用多进程 + 事件驱动的架构:

复制代码
                    ┌─────────────┐
                    │  Master进程  │
                    │  (管理进程)  │
                    └──────┬──────┘
                           │ 监控/热加载
        ┌──────────────────┼──────────────────┐
        │                  │                  │
        ▼                  ▼                  ▼
  ┌──────────┐      ┌──────────┐      ┌──────────┐
  │ Worker 1 │      │ Worker 2  │      │ Worker 3 │
  │ (工作进程)│      │ (工作进程)│      │ (工作进程)│
  └────┬─────┘      └────┬─────┘      └────┬─────┘
       │                  │                  │
       └──────────────────┼──────────────────┘
                          ▼
              ┌─────────────────────┐
              │  共享:连接池/缓存   │
              │  监听:所有worker   │
              │  共享同一端口       │
              └─────────────────────┘

Master进程职责:

  • 读取和验证配置文件
  • 管理worker进程(启动/停止/重启)
  • 接收外部信号(如reload、stop)
  • 热加载配置(不停服更新配置)

Worker进程职责:

  • 处理实际的请求
  • 每个worker都是独立进程,不会相互阻塞
  • 争抢accept()锁,高效处理并发

2.2 为什么Nginx性能这么高

秘密1:异步非阻塞I/O

Nginx使用epoll(Linux)、kqueue(FreeBSD)等高效I/O多路复用机制,一个worker可以同时处理成千上万个连接。

nginx 复制代码
# worker进程数配置
worker_processes auto;  # 自动等于CPU核心数

# 每个worker允许的连接数
worker_connections 65535;

# 使用epoll
use epoll;

# 开启高效异步模式
aio on;
thread_pool default threads=32 max_queue=65535;

秘密2:惊群问题的解决

当新连接到达时,多个worker都会被唤醒去抢这个连接,这就是"惊群"问题。Nginx通过accept_mutex锁来解决:

nginx 复制代码
# 开启accept互斥锁(默认开启)
accept_mutex on;

# 惊群等待时间
accept_mutex_delay 500ms;

秘密3:CPU亲和性绑定

将worker绑定到特定CPU核心,减少进程切换开销:

nginx 复制代码
worker_cpu_affinity auto;  # 自动绑定到各CPU核心

# 手动绑定(4核机器)
worker_cpu_affinity 0001 0010 0100 1000;

2.3 配置文件结构

nginx 复制代码
# 全局块:影响nginx整体运行
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    # events块:影响nginx与用户的连接
    worker_connections 65535;
    use epoll;
    multi_accept on;
}

http {
    # http块:HTTP服务器相关配置
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    
    # 日志格式
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
    
    access_log /var/log/nginx/access.log main;
    
    # 开启高效文件传输
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    
    # keepalive连接
    keepalive_timeout 65;
    keepalive_requests 10000;
    
    # gzip压缩
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css application/json application/javascript;
    
    # 上游服务器组
    upstream backend {
        server 192.168.1.10:8080 weight=3;
        server 192.168.1.11:8080 weight=2;
        server 192.168.1.12:8080 backup;  # 备用服务器
        
        keepalive 32;  # 长连接数
    }
    
    server {
        # server块:虚拟主机配置
        listen 80;
        server_name example.com;
        
        location / {
            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;
        }
    }
}

三、Upstream负载均衡策略:选择合适的分发方式

3.1 轮询(Round Robin)

默认策略,按顺序依次分配请求。

nginx 复制代码
upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

3.2 加权轮询(Weighted Round Robin)

根据权重比例分配请求,适合服务器性能不同的场景。

nginx 复制代码
upstream backend {
    # 机器1配置高,权重3
    server 192.168.1.10:8080 weight=3;
    # 机器2配置低,权重2
    server 192.168.1.11:8080 weight=2;
    # 机器3配置中等,权重2
    server 192.168.1.12:8080 weight=2;
}

3.3 IP哈希(IP Hash)

根据客户端IP计算哈希值,保证同一IP的请求总是发到同一台服务器。

nginx 复制代码
upstream backend {
    ip_hash;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

适用场景: 需要会话保持,但session存储在单机内存中的场景。

注意: 如果某台服务器宕机,其IP哈希会被重新分配,可能导致会话丢失。

3.4 最少连接(Least Connections)

将请求分配给当前连接数最少的服务器。

nginx 复制代码
upstream backend {
    least_conn;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

适用场景: 请求处理时间差异较大的场景。

3.5 哈希(Hash)

通用哈希,可基于任意key进行哈希。

nginx 复制代码
upstream backend {
    hash $request_uri consistent;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

consistent 参数开启一致性哈希,服务器增减时只会影响少量请求。

3.6 服务器状态设置

nginx 复制代码
upstream backend {
    server 192.168.1.10:8080 weight=3 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 weight=2;
    server 192.168.1.12:8080 backup;      # 备用服务器
    server 192.168.1.13:8080 down;         # 已下线
}
参数 说明
weight 权重
max_fails 最大失败次数,超过后标记为不可用
fail_timeout 失败后多久重新尝试(默认10秒)
backup 备用服务器,平时不接请求
down 标记为永久不可用

四、location匹配规则:搞清楚请求去哪了

4.1 匹配优先级

Nginx的location匹配优先级是一个经典难点:

复制代码
1. 精确匹配   location = / {}
2. 正则匹配   location ~ /images/ {}
3. 前缀匹配   location ^~ /images/ {}
4. 普通匹配   location / {}

优先级从高到低:

复制代码
匹配优先级(从高到低):
= 精确匹配     (完全相等,优先级最高)
^~ 前缀匹配   (匹配后不再检查正则)
~ 正则匹配    (按配置顺序,第一个匹配为准)
~* 不区分大小写正则
/ 通用匹配    (兜底)

4.2 配置示例

nginx 复制代码
server {
    listen 80;
    server_name example.com;
    
    # 1. 精确匹配 - 只匹配 /
    location = / {
        return 200 "精确匹配: /\n";
    }
    
    # 2. 正则匹配 - /api开头的请求
    location ~ ^/api/ {
        return 200 "正则匹配: /api/\n";
    }
    
    # 3. 前缀匹配 - /images开头的请求
    location ^~ /images/ {
        return 200 "前缀匹配: /images/\n";
    }
    
    # 4. 普通匹配 - 所有请求
    location / {
        return 200 "通用匹配: /\n";
    }
}

4.3 实际业务配置

nginx 复制代码
server {
    listen 80;
    server_name example.com;
    
    # 静态资源 - 长期缓存
    location ~* \.(jpg|jpeg|png|gif|ico|webp|svg)$ {
        root /var/www/static;
        expires 30d;
        add_header Cache-Control "public, max-age=2592000";
    }
    
    # CSS/JS - 中期缓存
    location ~* \.(css|js|woff|woff2|ttf)$ {
        root /var/www/static;
        expires 7d;
        add_header Cache-Control "public, max-age=604800";
    }
    
    # API接口 - 不缓存
    location /api/ {
        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;
    }
    
    # 管理后台 - 需要认证
    location ^~ /admin/ {
        proxy_pass http://admin_backend;
        # 认证逻辑
        auth_basic "Restricted";
        auth_basic_user_file /etc/nginx/.htpasswd;
    }
    
    # 默认处理
    location / {
        proxy_pass http://frontend;
    }
}

五、反向代理核心配置:让Nginx真正为你服务

5.1 最基本的代理配置

nginx 复制代码
location / {
    proxy_pass http://backend;
}

这看起来很简单,但背后发生的事情可不简单:

复制代码
客户端请求 → Nginx → 代理连接到后端 → 发送请求 → 接收响应 → 返回给客户端

5.2 必须传递的头信息

nginx 复制代码
location / {
    proxy_pass http://backend;
    
    # 传递真实IP
    proxy_set_header Host $host;              # 原始Host
    proxy_set_header X-Real-IP $remote_addr;  # 真实客户端IP
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  # 代理链IP
    proxy_set_header X-Forwarded-Proto $scheme;  # 原始协议(http/https)
    proxy_set_header X-Forwarded-Host $host;   # 原始主机
    proxy_set_header X-Forwarded-Port $server_port;  # 原始端口
}

为什么这些头重要?

没有这些配置,后端应用获取到的IP都是Nginx的IP,无法区分真实用户,也无法做IP限流、日志分析等。

5.3 超时配置

nginx 复制代码
location / {
    proxy_pass http://backend;
    
    # 连接超时
    proxy_connect_timeout 60s;
    
    # 发送请求超时
    proxy_send_timeout 60s;
    
    # 接收响应超时
    proxy_read_timeout 60s;
    
    # 缓冲区大小
    proxy_buffering on;
    proxy_buffer_size 4k;
    proxy_buffers 8 4k;
}

5.4 代理重试配置

nginx 复制代码
location / {
    proxy_pass http://backend;
    
    # 失败时重试到其他服务器
    proxy_next_upstream error timeout http_500 http_502 http_503;
    proxy_next_upstream_tries 3;
    proxy_next_upstream_timeout 30s;
}

5.5 HTTP/1.1和长连接

nginx 复制代码
upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    
    # 保持到上游的长连接
    keepalive 32;
}

location / {
    proxy_pass http://backend;
    
    # 必须使用HTTP/1.1才能支持长连接
    proxy_http_version 1.1;
    
    # 清空Connection头,启用长连接
    proxy_set_header Connection "";
}

长连接的效果对比:

复制代码
没有长连接(HTTP/1.0):
客户端 ←→ Nginx ←→ 后端
          每个请求都建立新TCP连接
          三次握手 + 四次挥手
          延迟增加 30-100ms

有长连接(HTTP/1.1):
客户端 ←→ Nginx ←→ 后端
          连接复用,请求pipeline
          延迟降低,吞吐提高

5.6 WebSocket代理

nginx 复制代码
location /ws/ {
    proxy_pass http://websocket_backend;
    
    # WebSocket必须
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    
    # WebSocket超时(要设置长一些)
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
}

六、缓存配置:Nginx也是缓存服务器

6.1 为什么要用Nginx缓存

复制代码
无缓存:
用户A请求 /api/data → Nginx → 后端(耗时500ms)
用户B请求 /api/data → Nginx → 后端(耗时500ms)
用户C请求 /api/data → Nginx → 后端(耗时500ms)

有缓存:
用户A请求 /api/data → Nginx缓存未命中 → 后端(500ms)→ 存入缓存
用户B请求 /api/data → Nginx缓存命中 → 直接返回(5ms)
用户C请求 /api/data → Nginx缓存命中 → 直接返回(5ms)

6.2 缓存配置

nginx 复制代码
# 在http块定义缓存路径和参数
proxy_cache_path /var/cache/nginx levels=1:2 
                 keys_zone=api_cache:100m
                 max_size=10g
                 inactive=60m
                 use_temp_path=off;

server {
    listen 80;
    
    location /api/ {
        proxy_pass http://backend;
        
        # 使用缓存
        proxy_cache api_cache;
        
        # 缓存key
        proxy_cache_key "$scheme$request_method$host$request_uri";
        
        # 缓存有效期
        proxy_cache_valid 200 5m;   # 200响应缓存5分钟
        proxy_cache_valid 404 1m;    # 404响应缓存1分钟
        
        # 缓存状态头(X-Cache-Status: HIT/MISS/BYPASS)
        add_header X-Cache-Status $upstream_cache_status;
        
        # 缓存绕过(请求头带Cache-Control: no-cache时绕过缓存)
        proxy_cache_bypass $http_cache_control;
        
        # 不缓存的响应码
        proxy_no_cache $http_pragma $http_authorization;
    }
}

6.3 缓存清理

nginx 复制代码
# 定义清理缓存的location
location ~ /purge(/.*) {
    proxy_cache_purge api_cache $scheme$request_method$host$1;
    return 200 "Cache purged\n";
}

# 使用方式:POST请求到 /purge/api/data
# curl -X POST http://example.com/purge/api/data

6.4 缓存问题排查

bash 复制代码
# 查看缓存文件
ls -la /var/cache/nginx/

# 查看缓存大小
du -sh /var/cache/nginx/

# 清理所有缓存
rm -rf /var/cache/nginx/*

七、限流:保护后端服务的利器

7.1 连接数限流

nginx 复制代码
# 基于IP的并发连接数限制
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

server {
    # 每个IP最多10个并发连接
    limit_conn conn_limit 10;
    
    # 超过限制时的日志
    limit_conn_log_level warn;
    
    # 超过限制时返回的状态码
    limit_conn_status 503;
}

7.2 请求速率限流

nginx 复制代码
# 基于IP的请求速率限制
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;

server {
    # 每秒最多10个请求
    # burst=20表示允许突发20个请求
    # nodelay表示突发请求立即处理,不延迟
    limit_req zone=req_limit burst=20 nodelay;
    
    limit_req_log_level warn;
    limit_req_status 429;
}

7.3 带宽限制

nginx 复制代码
server {
    # 限制每个连接的最大带宽为1MB/s
    limit_rate 1m;
    
    # 下载超过10MB后限速到512KB/s
    limit_rate_after 10m;
}

7.4 多维度限流

nginx 复制代码
# 按IP限流
limit_req_zone $binary_remote_addr zone=ip_limit:10m rate=10r/s;

# 按server限流(所有IP共享)
limit_req_zone $server_name zone=server_limit:10m rate=1000r/s;

# 按token限流(需要配合JWT等)
limit_req_zone $http_authorization zone=user_limit:10m rate=10r/s;

server {
    location /api/ {
        # 同时应用多个限流规则
        limit_req zone=ip_limit burst=20 nodelay;
        limit_req zone=server_limit burst=100;
        
        proxy_pass http://backend;
    }
}

八、HTTPS配置:安全是底线

8.1 基础HTTPS配置

nginx 复制代码
server {
    listen 443 ssl http2;
    server_name example.com;
    
    # 证书
    ssl_certificate /etc/nginx/ssl/example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;
    
    # 安全协议版本
    ssl_protocols TLSv1.2 TLSv1.3;
    
    # 安全加密套件
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers on;
    
    # Session缓存
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    
    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
}

8.2 免费证书Let's Encrypt

bash 复制代码
# 使用certbot申请证书
apt install certbot python3-certbot-nginx

# 自动配置Nginx
certbot --nginx -d example.com -d www.example.com

# 续期
certbot renew

8.3 HTTP跳转HTTPS

nginx 复制代码
# 方法1:301重定向
server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

# 方法2:使用if(不推荐)
server {
    listen 80;
    server_name example.com;
    if ($scheme = http) {
        return 301 https://$server_name$request_uri;
    }
}

# 方法3:HSTS(强制HTTPS)
server {
    listen 443 ssl;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}

8.4 TLS版本检测与加固

bash 复制代码
# 测试TLS配置
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3

# 查看支持的协议和密码套件
nmap --script ssl-enum-ciphers example.com

九、生产环境配置示例

9.1 完整的反向代理配置

nginx 复制代码
user nginx;
worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 65535;
    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;
    keepalive_requests 10000;
    
    # Gzip压缩
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
    
    # 限流
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;
    
    # 日志格式
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for" '
                    'rt=$request_time uct="$upstream_connect_time" '
                    'uht="$upstream_header_time" urt="$upstream_response_time"';
    
    access_log /var/log/nginx/access.log main;
    
    # 代理配置
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:100m max_size=10g inactive=60m;
    
    upstream backend {
        server 192.168.1.10:8080 weight=3 max_fails=3 fail_timeout=30s;
        server 192.168.1.11:8080 weight=3 max_fails=3 fail_timeout=30s;
        server 192.168.1.12:8080 backup;
        
        keepalive 32;
    }
    
    server {
        listen 80;
        listen 443 ssl http2;
        server_name example.com;
        
        ssl_certificate /etc/nginx/ssl/example.com.crt;
        ssl_certificate_key /etc/nginx/ssl/example.com.key;
        ssl_protocols TLSv1.2 TLSv1.3;
        
        # API接口
        location /api/ {
            limit_req zone=api_limit burst=200 nodelay;
            
            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 10s;
            proxy_send_timeout 30s;
            proxy_read_timeout 30s;
            
            proxy_cache api_cache;
            proxy_cache_valid 200 5m;
            add_header X-Cache-Status $upstream_cache_status;
        }
        
        # 静态资源
        location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2)$ {
            root /var/www/static;
            expires 30d;
            add_header Cache-Control "public, max-age=2592000";
        }
        
        # 默认
        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;
        }
    }
}

十、踩坑实录:这些年我踩过的Nginx坑

坑1:proxy_set_header Host配置错误导致后端取不到正确的Host

某次排查问题,发现后端应用获取到的Host总是"localhost",而不是配置的域名。

原因: proxy_set_header Host默认是host,但如果后端有虚拟主机配置,可能需要传host,但如果后端有虚拟主机配置,可能需要传host,但如果后端有虚拟主机配置,可能需要传host:$proxy_port。

解决: 显式配置 proxy_set_header Host $host;

坑2:正则location的优先级导致配置不生效

配置了一个正则location来处理/api/开头的请求,但发现不生效。

原因: 可能有更早的正则location匹配了,或者普通location先匹配了。

解决: 理解Nginx location优先级,或使用^~前缀匹配。

坑3:upstream配置顺序影响负载均衡

两台服务器配置完全相同,但一台负载总是比另一台高很多。

原因: IP Hash时,某些IP段恰好被哈希到同一台服务器。

解决: 切换到加权轮询或最小连接算法。

坑4:reload导致请求失败

执行nginx -s reload后,有几秒请求失败。

原因: 新旧worker进程切换时,accept锁的争抢导致短暂不可用。

解决: 使用nginx -s reload而不是kill -HUP,或使用优雅升级nginx -s reopen。

坑5:长连接没有生效

配置了keepalive,但发现性能没有提升。

原因: 忘记设置 proxy_http_version 1.1proxy_set_header Connection ""

解决: 这两个配置缺一不可。


十一、总结

Nginx是现代互联网架构的核心组件:

  • 反向代理:隐藏后端,提供统一的入口
  • 负载均衡:多种策略,灵活调度
  • 静态资源服务:高性能文件服务
  • 缓存:减轻后端压力
  • 限流:保护后端服务
  • HTTPS:安全加密通信

最佳实践:

  1. 合理配置worker进程数和连接数
  2. 正确传递代理头信息
  3. 使用长连接减少连接开销
  4. 配置限流保护后端
  5. 启用缓存提升性能
  6. HTTPS是标配,TLS 1.2起步

血的教训:

每次修改Nginx配置前,先用 nginx -t 语法检查,再 nginx -s reload 热加载。永远不要在没有检查的情况下直接重启。

思考题: 你们公司的Nginx目前用的哪种负载均衡策略?有没有遇到过头疼的Nginx问题?你的Nginx配置文件有多长?有没有可以优化的地方?


个人观点,仅供参考

相关推荐
空中海1 小时前
04 Stage 模型、系统能力与数据架构
架构·鸿蒙
Andya_net1 小时前
网络安全 | 浅析跨网访问对WAF防护架构的影响:网络流向与延迟对比
网络·web安全·架构
Qt程序员2 小时前
从协议到实战:HTTP 反向代理
linux·c++·websocket·nginx·http·反向代理·正向代理
im_AMBER2 小时前
Browser Agent 开发:从浏览器插件到Electron CDP
前端·javascript·架构·electron·agent
我滴老baby2 小时前
企业级工具链设计从单一工具到分层工具体系的架构实践
java·开发语言·架构
陈天伟教授2 小时前
图解人工智能(2)最智能
人工智能·安全·架构
折哥的程序人生 · 物流技术专研2 小时前
出版社物流WMS智能调度实战:从架构升级到机器学习落地
人工智能·机器学习·架构·人机交互
#山间清泉#3 小时前
VMWare虚拟机mac地址自定义配置
运维·macos·架构·vmware
湖南天硕国产SSD3 小时前
SSD主控架构到工业存储落地:天硕自研主控技术路径参照
架构·固态硬盘·天硕存储·ssd固态硬盘