HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 代理技术全栈手册

    • 代理技术全栈手册 - HiHuo
    • 原理篇

      • 第01章 代理是什么:正向 / 反向 / 透明 / 隧道的统一模型
      • 第02章 代理与网络层级:L3 / L4 / L5 / L7 在哪里截断流量
      • 第03章 一个请求穿过代理的一生:连接生命周期全景
    • 协议篇

      • 第04章 HTTP 代理协议:绝对 URI、CONNECT 隧道、转发头与连接池
      • 第05章 HTTPS 与 TLS 代理:终止 / 透传 / MITM / SNI / mTLS
      • 第06章 SOCKS 协议:SOCKS4/4a/5 与 UDP ASSOCIATE 报文级解析
      • 第07章 HTTP/2、gRPC 与 HTTP/3(QUIC) 代理的挑战
      • 第08章 代理自动配置:PAC / WPAD / 系统代理 / NO_PROXY
    • 层级与转发篇

      • 第09章 L4 代理:TCP/UDP 转发与连接级负载均衡
      • 第10章 L7 代理:协议感知与基于内容的路由
      • 第11章 透明代理:iptables REDIRECT/DNAT、TPROXY 与 eBPF 劫持
      • 第12章 数据搬运的艺术:splice / sendfile / 零拷贝 / io_uring
    • 组件横评篇

      • 第13章 Nginx / OpenResty:反向代理、upstream 与 Lua 可编程
      • 第14章 HAProxy:L4/L7、ACL、健康检查与 stick table
      • 第15章 Envoy:xDS 动态配置与 filter chain,为何是云原生数据面
      • 第16章 Traefik / Caddy:自动服务发现与自动 HTTPS
      • 第17章 Squid 与正向/缓存代理:企业出网、缓存与审计
      • 第18章 mitmproxy:抓包、改包、脚本化调试
      • 第19章 内网穿透与隧道:frp / gost / SSH 隧道 / ngrok
      • 第20章 科学上网生态的技术原理(技术中立)
    • 多语言手写篇

      • 第21章 Go:100 行手写 HTTP/CONNECT + SOCKS5 代理
      • 第22章 Rust:基于 tokio 的高性能 TCP 代理
      • 第23章 Python:asyncio 实现,适合调试与脚本
      • 第24章 C:epoll 裸写与零拷贝,及语言选型对比
    • 容器与K8s篇

      • 第25章 Docker 里的代理:HTTP_PROXY、build/pull 与 daemon 配置
      • 第26章 Sidecar 与流量劫持:Istio init-container 的 iptables 原理
      • 第27章 Ingress 与南北流量:Ingress-nginx 与 Gateway API
      • 第28章 Egress 与出网治理:出口网关、registry mirror、审计
      • 第29章 Service Mesh 数据面:Envoy Sidecar 全链路
    • 进阶篇

      • 第30章 可编程代理:Lua / Wasm / eBPF / xDS,代理的"软件定义"
      • 第31章 性能调优:并发模型、连接池、超时与重试、压测
      • 第32章 排错决策树:502 / 504 / 握手失败 / 环路 / 泄漏
      • 第33章 代理安全:开放代理、SSRF、凭证泄漏与攻击面
    • 底层机制篇

      • 第34章 代理的背压与流控:一个代理最难的部分
      • 第35章 socket 与 TCP 状态机:半关闭、超时、连接生命周期
      • 第36章 HTTP/2 帧、流控与 HPACK:h2 代理的内部机制
      • 第37章 负载均衡算法推导与韧性状态机
      • 第38章 Capstone:把玩具代理改造成生产级骨架
    • 综合实战篇

      • 第39章 企业多跳转发链:拓扑、协议矩阵与贯穿性难题
      • 第40章 端到端实战:把 6 类流量全代理通
      • 第41章 更刁钻的流量:gRPC、长轮询、WebRTC、大文件、双向流
      • 第42章 可落地完整参考实现:一套能跑的多协议转发栈
    • 附录

      • 附录 A:代理协议报文速查(HTTP / SOCKS / PAC / PROXY protocol)
      • 附录 B:组件选型决策树
      • 附录 C:抓包与命令速查

第13章 Nginx / OpenResty:反向代理、upstream 与 Lua 可编程

学习目标

  • 掌握 Nginx 作为反向代理的核心:proxy_pass、upstream、缓冲与缓存
  • 彻底搞懂 proxy_pass 的尾斜杠陷阱——Nginx 配置头号事故
  • 理解 OpenResty 如何用 Lua 把 Nginx 变成"可编程代理"
  • 明白 Nginx 的能力边界:何时够用、何时该换 Envoy

前置知识

  • 第10章 L7 代理、第05章 TLS 代理
  • 网络手册 · 负载均衡

原理

架构: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 -treload 前必 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 不擅长的领域。

Next
第14章 HAProxy:L4/L7、ACL、健康检查与 stick table