第13章 Nginx / OpenResty:反向代理、upstream 与 Lua 可编程
学习目标
- 掌握 Nginx 作为反向代理的核心:
proxy_pass、upstream、缓冲与缓存 - 彻底搞懂
proxy_pass的尾斜杠陷阱——Nginx 配置头号事故 - 理解 OpenResty 如何用 Lua 把 Nginx 变成"可编程代理"
- 明白 Nginx 的能力边界:何时够用、何时该换 Envoy
前置知识
原理
架构:master-worker + 事件驱动
Nginx 是 1 个 master 进程 + N 个 worker 进程,每个 worker 用 epoll 事件驱动、非阻塞处理成千上万连接(网络手册 · 多核网络优化)。这套模型让它以极低内存扛高并发,是它统治 Web 服务器/反向代理多年的根基。
作为代理,Nginx 的关键模块:
ngx_http_proxy_module:L7 HTTP 反向代理(proxy_pass)upstream:后端组 + 负载均衡 + 连接池ngx_stream_*:L4 TCP/UDP 代理(第09章 的stream块)proxy_cache:响应缓存
proxy_pass 的尾斜杠陷阱(必背)
这是 Nginx 最著名的坑,决定后端收到的路径:
# 情况 A:proxy_pass 带 URI(有尾斜杠 /)→ 替换掉 location 前缀
location /api/ {
proxy_pass http://backend/; # 请求 /api/users → 后端收到 /users
}
# 情况 B:proxy_pass 不带 URI(无尾斜杠)→ 保留完整原始路径
location /api/ {
proxy_pass http://backend; # 请求 /api/users → 后端收到 /api/users
}
记忆法:
proxy_pass后面有 URI(哪怕只是一个/)就做"前缀替换",没有就"原样透传"。无数次 404/路由错乱都源于这一个斜杠。改完务必用真实路径验证。
upstream:负载均衡与连接池
upstream backend {
least_conn; # 算法:rr / least_conn / ip_hash / hash / random
server 10.0.0.11:8080 weight=3 max_fails=3 fail_timeout=10s;
server 10.0.0.12:8080;
server 10.0.0.13:8080 backup; # 备用,主全挂才启用
keepalive 32; # 到后端的长连接池(关键性能项)
}
- 健康检查:开源版只有被动(
max_fails/fail_timeout,转发失败才摘);主动健康检查(周期探/healthz)需 Nginx Plus 或nginx_upstream_check_module - keepalive:到后端的连接池能省大量握手,但要配合
proxy_http_version 1.1+ 清空Connection头
缓冲与缓存
proxy_buffering on(默认):缓冲后端响应再发客户端——利于慢客户端,但流式/SSE/gRPC 要关(proxy_buffering off,第03章、第07章)proxy_cache:把响应缓存到磁盘,命中直接返回——L7 才能做(第10章)
OpenResty:把 Nginx 变成可编程代理
原生 Nginx 配置是声明式、静态的(改完要 reload)。OpenResty = Nginx + LuaJIT,在请求处理的各个阶段插入 Lua 代码,实现动态逻辑:
Nginx 请求处理阶段(可挂 Lua):
rewrite_by_lua → access_by_lua → content_by_lua → header_filter_by_lua → body_filter_by_lua → log_by_lua
(鉴权/限流) (改响应) (埋点)
balancer_by_lua ← 动态选后端(运行时改 upstream,无需 reload!)
这让 Nginx 能做:动态路由、JWT 鉴权、动态限流、灰度、调用外部服务决策——API 网关 Kong、APISIX 都基于 OpenResty(apiGateway 手册)。
能力边界:何时该换 Envoy
| 场景 | Nginx 合适吗 |
|---|---|
| 静态文件、Web 服务器、经典反向代理 | ✅ 最佳 |
| 中小规模负载均衡、TLS 卸载 | ✅ 很好 |
| 需 Lua 做动态逻辑 | ✅ 上 OpenResty |
| 超动态配置(秒级下发上千路由,无 reload) | ❌ 配置静态、reload 有成本 → 用 Envoy xDS(第15章) |
| gRPC/Service Mesh 数据面 | ⚠️ 可但不如 Envoy 原生 |
核心差异:Nginx 配置静态(reload 生效),Envoy 配置动态(xDS 热推)。云原生需要"控制面秒级下发"时,Envoy 胜出。
️ 实现 / 命令
实验一:一份生产级反向代理配置
upstream app {
least_conn;
server 10.0.0.11:8080 max_fails=3 fail_timeout=10s;
server 10.0.0.12:8080 max_fails=3 fail_timeout=10s;
keepalive 32;
}
server {
listen 443 ssl;
server_name app.example.com;
ssl_certificate /etc/nginx/certs/app.crt;
ssl_certificate_key /etc/nginx/certs/app.key;
location / {
proxy_pass http://app; # 注意:无尾斜杠,原样透传路径
proxy_http_version 1.1;
proxy_set_header Connection ""; # 配合 keepalive 清空逐跳头
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; # TLS 终止后告诉后端原始是 https
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
}
location /stream/ {
proxy_pass http://app;
proxy_buffering off; # 流式接口关缓冲
}
}
实验二:复现尾斜杠陷阱
# A 配置(带尾斜杠):/api/users → 后端 /users
# B 配置(无尾斜杠):/api/users → 后端 /api/users
# 用一个回显路径的后端验证
curl -s http://localhost/api/users # 看后端日志里实际收到的 path,对照上面两种
实验三:OpenResty 用 Lua 做请求鉴权
location /secure/ {
access_by_lua_block {
local token = ngx.var.http_authorization
if token ~= "Bearer secret123" then
ngx.exit(ngx.HTTP_UNAUTHORIZED) -- 401,请求不会到达后端
end
}
proxy_pass http://app;
}
实验四:热加载(reload 不断连)
nginx -t # 先测配置语法
nginx -s reload # master 启新 worker 接管新连接,老 worker 处理完存量连接再退出 → 不断连
排错
| 现象 | 根因 | 解决 |
|---|---|---|
| 后端 404 / 路径不对 | proxy_pass 尾斜杠用错 | 按"有 URI 则替换前缀"规则核对 |
| 后端拿到的 Host 不对 | 没设 proxy_set_header Host | 显式透传 $host |
| 流式接口卡住/不实时 | proxy_buffering on 缓冲了 | 该路径 proxy_buffering off |
| keepalive 不生效,连接频繁新建 | 没设 proxy_http_version 1.1 + 清 Connection | 两者都加 |
| 502 Bad Gateway | 后端不可达(第03章) | 查后端、proxy_pass 地址、SELinux |
| 后端挂了仍被分发 | 开源版只有被动健康检查 | 调 max_fails,或上主动检查模块 |
| reload 后偶发报错 | 改了配置没 nginx -t | reload 前必 nginx -t |
本章小结
- Nginx = master-worker + epoll 事件驱动,作代理靠
proxy_pass+upstream+ 缓冲/缓存。 - 尾斜杠陷阱:
proxy_pass带 URI 替换前缀、不带则原样透传——头号事故源。 - 开源版只有被动健康检查;keepalive 要配
proxy_http_version 1.1+ 清Connection。 - OpenResty 用 Lua 在各阶段插逻辑,把静态 Nginx 变可编程代理(Kong/APISIX 之基)。
- 边界:静态配置场景 Nginx 最佳;超动态下发该用 Envoy xDS。
下一章 第14章 HAProxy,看专业负载均衡器如何用 ACL 做 L4/L7 路由、用 stick table 做会话保持与限流——这些是 Nginx 不擅长的领域。