3.Nginx 反向代理与负载均衡
详解 Nginx 反向代理 proxy_pass 斜杠差异、真实 IP 透传配置,讲解 upstream 负载均衡策略,附带完整线上项目实战配置与排障问答。
Nginx 反向代理与负载均衡
一、反向代理核心概念
什么是反向代理
浏览器发请求给 Nginx,Nginx 把请求转发给后端服务器,后端返回的结果再原样返回给浏览器。对浏览器来说,它只知道 Nginx,不知道后面有 FastAPI、Nuxt 等后端服务在跑。Nginx 充当"中间人"角色。
为什么需要反向代理
- 一个 Nginx 托管多个网站(通过 server_name 区分)
- HTTPS 终止(浏览器和 Nginx 之间加密,Nginx 和后端之间可以明文)
- 负载均衡(同一网站多个后端实例分担压力)
- 安全隔离(后端不直接暴露给外网)
二、proxy_pass 斜杠问题(核心重点)
两种写法
# 写法A:proxy_pass 后面不带斜杠
location /api/ {
proxy_pass http://127.0.0.1:8001;
}
# 写法B:proxy_pass 后面带斜杠
location /api/ {
proxy_pass http://127.0.0.1:8001/;
}
区别(用户访问 /api/articles 时)
| 写法 | 转发目标 | 原因 |
|---|---|---|
| 不带斜杠(写法A) | http://127.0.0.1:8001/api/articles | location 路径原样拼接 |
| 带斜杠(写法B) | http://127.0.0.1:8001/articles | location 匹配的 /api/ 被截掉替换 |
规律
- 不带斜杠 → 原样拼接(
/api/+articles=/api/articles) - 带斜杠 → 截掉 location 匹配的部分,替换为 proxy_pass 后面的路径
为什么重要
博客用的是不带斜杠的写法:proxy_pass http://127.0.0.1:8001;
所以 FastAPI 后端的路由前缀必须写 /api/articles。
如果用了带斜杠写法,后端路由就得改成 /articles(没有 /api 前缀)了。两种写法都可以,但后端代码要配套。
三、proxy_set_header 请求头传递
为什么需要
Nginx 反向代理后,后端看到的请求来源全部是 127.0.0.1(Nginx 本机)。后端需要知道用户真实信息,必须通过 proxy_set_header 传递。
逐行解释
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;
| 指令 | 作用 | 不传会怎样 |
|---|---|---|
| Host $host | 告诉后端原始域名是 demo.test.com | 后端收到的是 127.0.0.1,可能导致域名路由错误 |
| X-Real-IP $remote_addr | 告诉后端真实用户 IP | 后端日志里所有用户都是 127.0.0.1 |
| X-Forwarded-For $proxy_add_x_forwarded_for | 用户 IP(支持多级代理链) | 同上,限流功能失效 |
| X-Forwarded-Proto $scheme | 告诉后端原始请求是 http 还是 https | 后端不知道用户走的什么协议,可能生成错误的跳转链接 |
X-Forwarded-For 详细说明
这个头记录的是请求经过的所有代理 IP,从离用户最近的开始:
用户(shturl.cc/kvuLa) → CDN(10.0.0.1) → Nginx(10.0.0.2) → 后端(127.0.0.1)
后端收到的 X-Forwarded-For 值:shturl.cc/kvuLa, 10.0.0.1
$proxy_add_x_forwarded_for 这个变量会自动把前面代理传过来的值拼接到后面,所以多级代理链也能正确记录用户真实 IP。
四、负载均衡 upstream
基本配置
当博客流量大了,可以跑多个后端实例:
upstream blog_api {
server 127.0.0.1:8001;
server 127.0.0.1:8002;
}
server {
location /api/ {
proxy_pass http://blog_api; # 指向 upstream 名字,不是直接写 IP
}
}
三种负载均衡算法
1. 轮询(默认,不用写算法配置)
upstream blog_api {
server 127.0.0.1:8001;
server 127.0.0.1:8002;
}
请求依次分发:第1个给8001,第2个给8002,第3个给8001...适合一般场景,最常用。
2. least_conn(最少连接数)
upstream blog_api {
least_conn;
server 127.0.0.1:8001;
server 127.0.0.1:8002;
}
哪个后端当前连接数最少,请求就给谁。适合某个请求处理时间特别长的场景(比如大文件上传、长时间计算)。
3. ip_hash(同一IP始终同一后端)
upstream blog_api {
ip_hash;
server 127.0.0.1:8001;
server 127.0.0.1:8002;
}
对用户 IP 做哈希,同一个 IP 永远访问同一个后端。适合需要 session 保持的场景(比如登录状态)。缺点是如果某个用户流量特别大,会把单个后端压垮。
upstream 参数详解
| 参数 | 作用 | 示例 |
|---|---|---|
| weight=N | 权重,数字越大分到的请求越多 | weight=7 占 70%,weight=3 占 30% |
| backup | 备用服务器,所有正常 server 挂了才启用 | 正常不接收流量,只做热备 |
| max_fails=N | 最大失败次数(在 fail_timeout 时间窗口内) | 默认是 1 |
| fail_timeout=Ns | 失败计数窗口 + 暂停时间 | 超过后标记为不可用,等 N 秒再重试 |
| down | 手动标记该 server 下线,不再分配流量 | 维护时使用 |
参数组合效果
max_fails + fail_timeout 联合:
15秒内连续失败3次 → 该 server 被标记为不可用 → 过15秒后重新尝试 → 如果恢复就继续分配,没恢复就继续暂停。
weight 计算方法:
所有 server 的 weight 之和为总权重,每个 server 承担的流量比例 = 它的 weight / 总 weight。
例:weight=7 和 weight=3,总权重 10,7/10=70%,3/10=30%。
实战练习题
场景:3台服务器集群,A(10.1) 性能强承担70%流量,B(10.2) 性能一般承担30%,C(10.3) 仅作热备。A和B在15秒内3次连接失败则暂停分配。
upstream web_cluster {
server 192.168.10.1 weight=7 max_fails=3 fail_timeout=15;
server 192.168.10.2 weight=3 max_fails=3 fail_timeout=15;
server 192.168.10.3 backup;
}
逐行解析:
- weight=7 / weight=3 → 按 7:3 分配流量(70%:30%)
- max_fails=3 → 15秒内最多允许失败3次
- fail_timeout=15 → 失败窗口15秒,超过后暂停15秒再重试
- backup → A和B全挂了才启用C
五、博客的完整反向代理配置
# /etc/nginx/conf.d/demo.conf
server {
server_name demo.test.com www.demo.test.com;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/demo.test.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/demo.test.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# API 请求 → FastAPI 后端
location /api/ {
proxy_pass http://127.0.0.1:8001;
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;
}
# 静态文件直接服务(不经过后端)
location /uploads/ {
alias /root/blog/data/uploads/;
}
# 站点地图 → FastAPI
location = /sitemap.xml {
proxy_pass http://127.0.0.1:8001/api/sitemap.xml;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 百度验证文件
location ~ ^/baidu_verify_ {
root /root/blog/frontend/.output/public;
}
# 其他所有请求 → Nuxt 前端
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
}
请求流程图
用户访问 https://demo.test.com/api/articles
浏览器 → Nginx(443)
├── location /api/ → proxy_pass http://127.0.0.1:8001
│ → FastAPI 收到 /api/articles → 查数据库 → 返回 JSON
│ → 原路返回给浏览器
用户访问 https://demo.test.com/about
浏览器 → Nginx(443)
├── location / → proxy_pass http://127.0.0.1:3000
│ → Nuxt SSR 渲染 → 返回 HTML
│ → 原路返回给浏览器
用户访问 https://demo.test.com/uploads/image.jpg
浏览器 → Nginx(443)
├── location /uploads/ → alias /root/blog/data/uploads/
│ → 直接读取磁盘文件返回
六、问答
Q:反向代理是什么?
Nginx 接收客户端请求,转发给后端服务器,再把响应返回给客户端。客户端不知道后端的存在。
Q:proxy_pass 带斜杠和不带斜杠的区别?
不带斜杠:原样拼接 location 路径。带斜杠:截掉 location 匹配的部分。举个例子:访问 /api/articles,不带斜杠转发到 /api/articles,带斜杠转发到 /articles。
Q:为什么要传 Host、X-Real-IP、X-Forwarded-For?
Nginx 反向代理后,后端只看到 127.0.0.1。需要这些头来告诉后端原始域名、用户真实 IP、使用的协议。
Q:负载均衡有几种方式?
轮询(默认)、least_conn(最少连接)、ip_hash(IP哈希保持会话)。
Q:502 Bad Gateway 是什么问题?
Nginx 转发请求后,后端没有正常响应。先 curl 后端端口看通不通,再看后端进程是否活着。
说明:文中域名、IP均为教学演示虚拟地址,无真实线上服务
评论
暂无评论,来写第一条吧
