Nginx 为什么适合做反向代理

当一个网站从“一台服务器跑一个服务”长到多台实例、多种服务时,最早暴露的问题就是入口。用户的请求总得先到一个固定地址,再由它决定转发给谁——这个入口角色,Nginx 是最常见的承担者。

Nginx 采用事件驱动、异步非阻塞的架构,用少量 worker 进程就能扛住上万并发连接,内存占用还低。相比“每个请求占一个线程”的传统服务器,它特别适合三件事:静态资源服务、反向代理和负载均衡。

反向代理指代理服务器站在服务端一侧,代表后端服务接收外部请求。客户端并不知道真正处理请求的是哪台机器,它只看到一个统一入口。这样做的好处是多方面的:

  • 隐藏后端拓扑,对外只暴露一个域名和端口,扩容、迁移都不影响客户端。
  • 统一做 TLS 终止,证书只在入口维护一份,后端服务专注业务。
  • 集中实现缓存、压缩、限流、跨域与访问日志,避免每个服务重复造轮子。
  • 支持灰度与蓝绿发布:把一部分流量按权重或规则导到新版本。
本文核心知识点
  • 反向代理proxy_pass 把请求转发到后端,并用 proxy_set_header 传递真实客户端信息。
  • upstream把多台后端组成一个上游组,用轮询、权重、IP 哈希等策略分流。
  • 故障转移proxy_next_upstream 让某台后端异常时自动切到下一台。
  • 长连接keepalive 复用与后端的连接,显著降低 TIME_WAIT 与延迟。
Nginx 反向代理示意:蓝色渐变背景上的网关模块向三台服务器扇形分流的数据管道
反向代理与负载均衡:入口网关把来自客户端的流量按策略分摊到多台后端服务器,这正是 Nginx 的核心职责。

正向代理与反向代理的区别

“代理”这个词容易混淆,关键在于它代表谁。正向代理代表客户端,反向代理代表服务端。前者需要客户端显式配置,后者对客户端完全透明。

维度正向代理反向代理
代表对象客户端服务端
部署位置靠近客户端靠近服务端
客户端是否感知需要显式配置代理地址无感知,只看到统一入口
隐藏对象隐藏客户端真实 IP隐藏后端服务器拓扑
典型用途企业出口、爬虫出口、访问控制负载均衡、网关、TLS 终止

理解了这层区别,就能明白为什么“给后端加 Nginx”是最常见的架构动作:它是服务端的门面,客户端的每一次请求都先到这里,再由它分发。

安装与配置文件组织

在主流 Linux 发行版上安装 Nginx 只需要一条命令。装完后先确认版本与编译参数,尤其是是否带 SSL 模块,避免后面配 HTTPS 时才发现不支持。

# 以 Ubuntu / Debian 为例
sudo apt update
sudo apt install -y nginx

nginx -v      # 查看版本号
nginx -V      # 查看编译参数,确认包含 --with-http_ssl_module
nginx -t      # 校验配置文件语法,改配置后必做

sudo systemctl enable --now nginx
sudo systemctl reload nginx    # 平滑重载,不中断已有连接

配置文件的组织方式值得先说清楚,否则很容易改错地方。主配置 /etc/nginx/nginx.conf 里通过 include 引入了各站点配置;业务配置建议单独放 /etc/nginx/conf.d/*.conf,每个站点一个文件。这样既方便版本管理,也方便按需启用或禁用。

# /etc/nginx/nginx.conf 中的关键片段
worker_processes auto;            # 通常等于 CPU 核心数
events {
    worker_connections 10240;     # 单个 worker 允许的最大连接数
}
http {
    include       mime.types;
    include       /etc/nginx/conf.d/*.conf;   # 站点配置都放这里
    sendfile      on;
    keepalive_timeout 65;
}

最小可用的反向代理配置

下面是一个可以直接放进 /etc/nginx/conf.d/app.conf 的完整示例:把 api.example.com 的请求转发到两台后端实例上。

upstream app_backend {
    server 10.0.0.11:8080;
    server 10.0.0.12:8080;
}

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

    location / {
        proxy_pass http://app_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_set_header X-Forwarded-Proto $scheme;
    }
}

这段配置里,最容易漏掉的是那四行 proxy_set_header。默认情况下,Nginx 转发时会把 Host 改成上游名字,后端拿到的客户端 IP 也变成 Nginx 的地址。结果就是:后端拼接绝对地址做重定向时出错、日志里全是同一个来源 IP、按 IP 做的限流失效。把 Host、X-Real-IP、X-Forwarded-For、X-Forwarded-Proto 显式传下去,是反向代理的第一条军规。

upstream 负载均衡策略

Nginx 内置了多种负载均衡算法,不需要额外插件。选哪一种,取决于后端是否为无状态、实例规格是否一致、请求耗时是否均匀。

策略指令写法适用场景
轮询(默认)server a; server b;实例规格一致、无状态服务
权重server a weight=3;混合规格、灰度发布
IP 哈希ip_hash;需要会话保持的有状态服务
最少连接least_conn;请求耗时差异较大
一致性哈希hash $request_uri consistent;提高缓存命中率

下面这个 upstream 组把权重、备用节点和长连接组合在一起,是生产环境里很常见的写法:

upstream app_backend {
    least_conn;                        # 优先选连接数最少的实例
    server 10.0.0.11:8080 weight=3;    # 权重 3,承接更多流量
    server 10.0.0.12:8080 weight=1;
    server 10.0.0.13:8080 backup;      # 备用节点,主节点全部不可用时启用
    keepalive 32;                      # 与后端保持 32 条空闲长连接
}

要注意:一旦启用了 keepalive,location 里还需要补上 proxy_http_version 1.1; 与 proxy_set_header Connection "";,否则长连接无法真正复用。权重适合做小流量灰度,比如先给新版本 10% 的流量观察一阵,再逐步放大。

健康检查与故障转移

负载均衡的另一半是容错:当某台后端出问题时,流量要能自动绕开它。开源版 Nginx 提供的是被动健康检查——只有当请求失败时才会把实例标记为不可用,max_fails 与 fail_timeout 控制判定阈值。

upstream app_backend {
    server 10.0.0.11:8080 max_fails=3 fail_timeout=10s;
    server 10.0.0.12:8080 max_fails=3 fail_timeout=10s;
}

location / {
    proxy_pass http://app_backend;

    proxy_connect_timeout 2s;      # 连接后端超时
    proxy_read_timeout    10s;     # 读取响应超时

    # 命中这些错误时,自动切到下一个上游实例
    proxy_next_upstream error timeout http_502 http_503 http_504;
    proxy_next_upstream_tries 2;                  # 最多尝试 2 个实例
    proxy_next_upstream_timeout 5s;               # 总重试时间上限
}

这里有一个必须提醒的坑:proxy_next_upstream 默认不包含非幂等请求的重试语义,对 POST 盲目重试可能造成重复下单。因此它更适合配置在查询类接口上;写接口的重试,应当交给带幂等键的业务逻辑处理,而不是交给网关。