Nginx 与反向代理 / Nginx and Reverse Proxy
📅 创建时间:2026-07-28 🏷️ 标签:#Nginx #ReverseProxy #LoadBalancing #SSL #HTTP2 #RateLimiting 📚 前置知识:[[00-overview]](基础设施组件全景)
📋 本章目标
- 理解 Nginx 的 Master/Worker 进程模型与事件驱动架构
- 掌握反向代理的核心配置(proxy_pass、负载均衡算法、健康检查)
- 能够正确配置 SSL/TLS 终止、HTTP/2、OCSP Stapling
- 理解静态资源服务的最佳实践(缓存策略、压缩、条件请求)
- 掌握 Nginx 限流(limit_req/limit_conn)与基础安全配置
- 理解 Nginx 变量系统与常用脚本模式
- 能够对比 Nginx、Caddy、Traefik 并做出合理选型
第1部分:Nginx 架构
1.1 Master/Worker 进程模型
┌─────────────────────────────────────────────────────────────┐
│ Nginx 进程模型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Master Process(PID=1,以 root 运行) │ │
│ │ • 读取和验证配置文件 │ │
│ │ • 创建和管理 Worker 进程 │ │
│ │ • 处理信号(reload / reopen / stop) │ │
│ │ • 不处理任何客户端请求 │ │
│ └──────┬───────────────┬───────────────┬──────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Worker 1 │ │ Worker 2 │ │ Worker N │ │
│ │ (非 root) │ │ (非 root) │ │ (非 root) │ │
│ │ │ │ │ │ │ │
│ │ epoll/kqueue│ │ epoll/kqueue│ │ epoll/kqueue│ │
│ │ 事件循环 │ │ 事件循环 │ │ 事件循环 │ │
│ │ │ │ │ │ │ │
│ │ 处理请求 │ │ 处理请求 │ │ 处理请求 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ 关键设计决策: │
│ • worker_processes auto; ← 自动设为 CPU 核心数 │
│ • worker_connections 1024; ← 每个 Worker 最大连接数 │
│ • 最大并发 = worker_processes × worker_connections │
│ • Worker 之间不共享内存,通过共享内存区域(shared mem) │
│ 实现限流、缓存等需要跨 Worker 协作的功能 │
│ │
└─────────────────────────────────────────────────────────────┘1.2 事件驱动 vs 线程模型
┌─────────────────────────────────────────────────────────────┐
│ 事件驱动 vs 线程模型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Apache(线程/进程模型): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 每个请求 → 创建一个线程/进程 │ │
│ │ 1000 并发 → 1000 个线程 → 大量上下文切换 │ │
│ │ 长连接(SSE/WebSocket)→ 线程被长时间占用 │ │
│ │ 内存:每个线程 ~2MB 栈空间 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Nginx(事件驱动模型): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 每个 Worker 是一个单线程事件循环 │ │
│ │ 1000 并发 → 1 个线程 + 1000 个事件 │ │
│ │ 通过 epoll(Linux)/ kqueue(macOS)监听事件 │ │
│ │ epoll 核心:只返回"有事件发生"的 fd │ │
│ │ │ │ │
│ │ │ select/poll(老方案):遍历所有 fd,O(n) │ │
│ │ │ epoll:只返回活跃 fd,O(1) per event │ │
│ │ 内存:无需为每个连接创建线程,内存占用极小 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 这也是为什么 Nginx 能做到 C10K(1 万并发),甚至 │
│ C100K(10 万并发)而 Apache 在几百并发就崩溃的原因。 │
│ │
└─────────────────────────────────────────────────────────────┘1.3 非阻塞 I/O 的工作方式
┌─────────────────────────────────────────────────────────────┐
│ 非阻塞 I/O 流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 传统阻塞 I/O(BIO): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ read(fd, buf) → 内核准备数据 → 等待... 等待... │ │
│ │ → 数据就绪 → 复制到用户空间 → 返回 │ │
│ │ 线程在"等待"期间什么都做不了(阻塞) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Nginx 非阻塞 I/O + epoll: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 把 fd 设为非阻塞模式(O_NONBLOCK) │ │
│ │ 2. 把 fd 注册到 epoll,告诉内核"就绪了叫我" │ │
│ │ 3. read(fd) → 数据未就绪 → 返回 EAGAIN(不等待) │ │
│ │ 4. Worker 继续处理其他事件 │ │
│ │ 5. 内核通知:"fd 就绪了" → Worker 再回来 read │ │
│ │ │ │
│ │ 关键:一个线程处理成千上万个连接,不做无谓等待。 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Nginx 的配置体现这个思维: │
│ sendfile on; ← 零拷贝:数据从磁盘到 socket │
│ tcp_nopush on; ← 攒够一个包再发,减少网络包数 │
│ tcp_nodelay on; ← 小包立即发送(对 keep-alive 重要) │
│ │
└─────────────────────────────────────────────────────────────┘第2部分:反向代理配置
2.1 核心指令:proxy_pass
┌─────────────────────────────────────────────────────────────┐
│ 反向代理配置骨架 │
├─────────────────────────────────────────────────────────────┤
│ │
│ http { │
│ │
│ upstream backend { ← 定义后端服务器组 │
│ server 10.0.1.1:3000 weight=3; ← 权重 3 │
│ server 10.0.1.2:3000 weight=1; ← 权重 1 │
│ server 10.0.1.3:3000 backup; ← 备用,其他挂了才用│
│ keepalive 32; ← 到后端的连接池 │
│ } │
│ │
│ server { │
│ listen 443 ssl http2; │
│ server_name api.example.com; │
│ │
│ location / { │
│ proxy_pass http://backend; ← 转发到 upstream │
│ proxy_http_version 1.1; ← HTTP/1.1 到后端 │
│ proxy_set_header Connection ""; ← 清理 Connection头 │
│ proxy_set_header Host $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_pass 末尾 / 的陷阱: │
│ location /api/ { proxy_pass http://backend/; } │
│ → /api/users → /users(/api 被替换) │
│ location /api/ { proxy_pass http://backend; } │
│ → /api/users → /api/users(原样转发) │
│ │
└─────────────────────────────────────────────────────────────┘2.2 负载均衡算法
┌─────────────────────────────────────────────────────────────┐
│ 负载均衡算法对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ │ 算法 │ 指令 │ 适合场景 │
│ ├──────────────────┼──────────────────┼────────────────┤
│ │ 轮询(默认) │ (无需指定) │ 后端性能一致 │
│ │ Round-Robin │ 或 weight=N │ 通用场景 │
│ │ │ │ 支持权重 │
│ ├──────────────────┼──────────────────┼────────────────┤
│ │ 最少连接 │ least_conn; │ 请求耗时差异大 │
│ │ Least Connections│ │ 长连接场景 │
│ ├──────────────────┼──────────────────┼────────────────┤
│ │ IP 哈希 │ ip_hash; │ 需要会话保持 │
│ │ IP Hash │ │ 无状态服务慎用 │
│ ├──────────────────┼──────────────────┼────────────────┤
│ │ 通用哈希 │ hash $request_ │ 按某变量哈希 │
│ │ Generic Hash │ uri consistent; │ 缓存友好 │
│ ├──────────────────┼──────────────────┼────────────────┤
│ │ 最短响应时间 │ least_time │ Nginx Plus │
│ │ Least Time │ header; │ (商业版) │
│ │
│ 示例配置: │
│ upstream backend { │
│ least_conn; ← 算法 │
│ server 10.0.1.1:3000 weight=5; ← 权重 │
│ server 10.0.1.2:3000 weight=3; │
│ server 10.0.1.3:3000 max_fails=3 ← 健康检查 │
│ fail_timeout=30s; ← 故障冷却 │
│ } │
│ │
│ ip_hash 的坑:如果用户走代理(NAT),所有用户 IP 都一样, │
│ 会全部打到同一台后端,负载均衡失效。 │
│ │
└─────────────────────────────────────────────────────────────┘2.3 健康检查与故障转移
┌─────────────────────────────────────────────────────────────┐
│ 被动健康检查 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 被动检查(开源版自带): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ server 10.0.1.1:3000 max_fails=3 fail_timeout=30s;│ │
│ │ • max_fails=3: 30s 内失败 3 次 → 标记为 down │ │
│ │ • fail_timeout=30s: 冷却 30s 后重新尝试 │ │
│ │ • 只在有真实请求时才检查(被动) │ │
│ │ • 缺点:故障期间所有请求仍然会被转发到挂掉的节点 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 主动检查(Nginx Plus 商业版 / 开源插件): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ upstream backend { │ │
│ │ server 10.0.1.1:3000; │ │
│ │ server 10.0.1.2:3000; │ │
│ │ │ │
│ │ health_check interval=5s │ │
│ │ fails=3 │ │
│ │ passes=2 │ │
│ │ uri=/health; │ │
│ │ } │ │
│ │ • Nginx 主动定期请求 /health 端点 │ │
│ │ • 及时感知故障,不影响正常请求 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 生产建议: │
│ • 健康检查端点应该轻量(不查数据库,只返回 200) │
│ • 区分存活检查(liveness)和就绪检查(readiness) │
│ • 后端不要因为健康检查产生副作用(幂等设计) │
│ │
└─────────────────────────────────────────────────────────────┘第3部分:SSL/TLS 终止
3.1 概念与配置
┌─────────────────────────────────────────────────────────────┐
│ SSL/TLS 终止架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 无 SSL 终止(Pass-through): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 客户端 ←── HTTPS ──→ 后端服务(需要证书) │ │
│ │ • 每个后端都要配置 SSL 证书 │ │
│ │ • 后端承担加解密开销 │ │
│ │ • Nginx 在 L4 层透明转发(stream 模块) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ SSL 终止(Termination): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 客户端 ←── HTTPS ──→ [Nginx 解密] ←── HTTP ──→ 后端 │ │
│ │ • 证书只在 Nginx 上配置 │ │
│ │ • 后端只需要处理 HTTP │ │
│ │ • 解密/加密开销集中在 Nginx │ │
│ │ • 内网流量不加密(通常可接受) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ SSL 重加密(Re-encryption): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 客户端 ←── HTTPS ──→ [Nginx] ←── HTTPS ──→ 后端 │ │
│ │ • 内网流量也加密(零信任架构) │ │
│ │ • 后端也需要配置证书(可以是内网 CA 签发的) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘3.2 证书配置
┌─────────────────────────────────────────────────────────────┐
│ SSL 配置最佳实践 │
├─────────────────────────────────────────────────────────────┤
│ │
│ server { │
│ listen 443 ssl http2; ← 同时启用 HTTP/2 │
│ server_name example.com; │
│ │
│ ssl_certificate /path/to/fullchain.pem; │
│ ssl_certificate_key /path/to/privkey.pem; │
│ │
│ ssl_protocols TLSv1.2 TLSv1.3; ← 禁用 TLSv1.0/1.1 │
│ │
│ ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256: │
│ ECDHE-RSA-AES128-GCM-SHA256: │
│ ECDHE-ECDSA-AES256-GCM-SHA384: │
│ ECDHE-RSA-AES256-GCM-SHA384; │
│ │
│ ssl_prefer_server_ciphers on; ← 优先使用服务器端密码套件│
│ │
│ ssl_session_cache shared:SSL:10m; ← 10MB 可存 ~4 万会话│
│ ssl_session_timeout 10m; ← 会话复用时间 │
│ │
│ ssl_stapling on; ← OCSP Stapling │
│ ssl_stapling_verify on; ← 验证 OCSP 响应 │
│ resolver 8.8.8.8 valid=300s; ← DNS 解析器 │
│ } │
│ │
│ OCSP Stapling 的价值: │
│ 传统 OCSP:浏览器需要查询 CA 的 OCSP 服务器验证证书状态 │
│ → 隐私泄露 + 延迟 + CA 服务器可能挂掉 │
│ OCSP Stapling:Nginx 定期从 CA 获取 OCSP 响应并"装订" │
│ 在 TLS 握手中发送给浏览器 │
│ → 更快、更隐私、不依赖 CA 实时可用性 │
│ │
└─────────────────────────────────────────────────────────────┘3.3 HTTP/2 与 Nginx
┌─────────────────────────────────────────────────────────────┐
│ HTTP/2 关键改进 │
├─────────────────────────────────────────────────────────────┤
│ │
│ HTTP/1.1 的问题: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 浏览器 → 6 个并发连接(每个域名) │ │
│ │ 每个连接 → 串行请求(Head-of-Line Blocking) │ │
│ │ 每个请求 → 独立的 TCP 连接(或 Keep-Alive 复用) │ │
│ │ N 个资源 → 需要 N 个请求-响应往返 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ HTTP/2 的改进: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 浏览器 → 1 个 TCP 连接(Multiplexing) │ │
│ │ 所有请求 → 在同一个连接上并行交错传输 │ │
│ │ Server Push → 服务端可主动推送资源 │ │
│ │ HPACK → 头部压缩,减少带宽 │ │
│ │ 二进制帧 → 更高效的解析 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Nginx HTTP/2 配置: │
│ listen 443 ssl http2; │
│ │
│ 注意事项: │
│ • HTTP/2 只在 HTTPS 下工作(浏览器不实现 h2c 明文) │
│ • Nginx 1.25+ 开始支持 HTTP/3(QUIC) │
│ • Server Push 在实践中用得很少(难以预测缓存状态) │
│ │
└─────────────────────────────────────────────────────────────┘第4部分:静态资源服务与缓存
4.1 静态资源配置
┌─────────────────────────────────────────────────────────────┐
│ 静态资源服务配置 │
├─────────────────────────────────────────────────────────────┤
│ │
│ location /static/ { │
│ alias /var/www/static/; ← 物理路径映射 │
│ expires 30d; ← Cache-Control: max-age │
│ add_header Cache-Control "public, immutable"; │
│ │
│ gzip on; ← 启用 gzip │
│ gzip_comp_level 6; ← 压缩级别 1-9 │
│ gzip_min_length 1024; ← 小于 1KB 不压缩 │
│ gzip_types text/plain text/css application/javascript │
│ application/json image/svg+xml; │
│ gzip_vary on; ← 添加 Vary: Accept-Encoding│
│ │
│ brotli on; ← 启用 Brotli(需编译模块) │
│ brotli_comp_level 6; ← Brotli 比 gzip 小 ~20% │
│ brotli_types text/plain text/css application/javascript; │
│ } │
│ │
│ expires 指令的效果: │
│ • expires 30d; → max-age=2592000 │
│ • expires -1; → Cache-Control: no-cache │
│ • expires epoch; → Cache-Control: no-cache │
│ • expires off; → 不添加 Cache-Control │
│ │
│ 缓存策略建议: │
│ │ 资源类型 │ 缓存策略 │ 原因 │
│ ├───────────────┼──────────────────────┼───────────────┤
│ │ 带哈希的静态资源│ 1 年 + immutable │ 内容变了文件名│
│ │ (app.a1b2.js) │ │ 就变 │
│ ├───────────────┼──────────────────────┼───────────────┤
│ │ HTML 入口文件 │ no-cache │ 需要验证更新 │
│ ├───────────────┼──────────────────────┼───────────────┤
│ │ API 响应 │ no-store(或短 TTL) │ 动态数据 │
│ │
└─────────────────────────────────────────────────────────────┘4.2 gzip vs Brotli 对比
┌─────────────────────────────────────────────────────────────┐
│ gzip vs Brotli │
├─────────────────────────────────────────────────────────────┤
│ │
│ │ 维度 │ gzip │ Brotli │
│ ├──────────────┼──────────────┼─────────────────────────┤
│ │ 压缩率 │ 中等(基线) │ 比 gzip 小 15-25% │
│ ├──────────────┼──────────────┼─────────────────────────┤
│ │ 压缩速度 │ 快 │ 较慢(level 11 很慢) │
│ ├──────────────┼──────────────┼─────────────────────────┤
│ │ 解压速度 │ 快 │ 与 gzip 相当 │
│ ├──────────────┼──────────────┼─────────────────────────┤
│ │ 浏览器支持 │ 100% │ ~97%(所有现代浏览器) │
│ ├──────────────┼──────────────┼─────────────────────────┤
│ │ Nginx 支持 │ 内置 ngx_http│ 需编译 ngx_brotli 模块 │
│ │ │ _gzip_module │ 或使用 Nginx Plus │
│ │
│ 生产建议:同时启用 gzip 和 Brotli。 │
│ 浏览器发送 Accept-Encoding: br, gzip → Nginx 优先用 Brotli │
│ 不支持 Brotli 的旧浏览器 → 降级到 gzip。 │
│ │
│ 静态资源预压缩(最高效方案): │
│ gzip_static on; ← 请求 .js 时自动找 .js.gz │
│ brotli_static on; ← 请求 .js 时自动找 .js.br │
│ 构建时压缩一次,Nginx 直接返回,零 CPU 开销。 │
│ │
└─────────────────────────────────────────────────────────────┘第5部分:限流与安全
5.1 限流配置
┌─────────────────────────────────────────────────────────────┐
│ Nginx 限流架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ http { │
│ │
│ # 定义限流区域(共享内存,跨 Worker) │
│ limit_req_zone $binary_remote_addr zone=api_limit:10m │
│ rate=10r/s; │
│ limit_req_zone $server_name zone=server_limit:10m │
│ rate=1000r/s; │
│ │
│ # 定义连接数限制区域 │
│ limit_conn_zone $binary_remote_addr zone=conn_limit:10m; │
│ │
│ server { │
│ location /api/ { │
│ # 请求速率限制 │
│ limit_req zone=api_limit burst=20 nodelay; │
│ # burst=20: 允许 20 个请求排队 │
│ # nodelay: 超出 rate 时立即返回 503(不排队) │
│ │
│ # 连接数限制 │
│ limit_conn conn_limit 10; ← 同一 IP 最多 10 个连接│
│ │
│ # 请求大小限制 │
│ client_max_body_size 10m; ← 上传最大 10MB │
│ client_body_timeout 60s; ← 请求体超时 │
│ client_header_timeout 10s; ← 请求头超时 │
│ } │
│ } │
│ } │
│ │
│ limit_req 的 burst + nodelay 行为: │
│ rate=10r/s(每秒 10 个 = 100ms 一个) │
│ burst=20(队列最多排 20 个) │
│ • 无 nodelay:排队的请求慢慢处理(按 rate 速度释放) │
│ • 有 nodelay:rate 以内的立即处理,burst 内的也立即处理 │
│ 超过 burst → 503 │
│ │
└─────────────────────────────────────────────────────────────┘5.2 IP 黑白名单与 WAF 基础
┌─────────────────────────────────────────────────────────────┐
│ IP 访问控制 + WAF │
├─────────────────────────────────────────────────────────────┤
│ │
│ # IP 白名单(管理后台只允许公司 IP 访问) │
│ location /admin { │
│ allow 10.0.0.0/8; ← 允许内网 │
│ allow 203.0.113.5; ← 公司 VPN IP │
│ deny all; ← 拒绝所有其他 │
│ } │
│ │
│ # IP 黑名单(封禁恶意 IP 网段) │
│ location / { │
│ deny 192.168.1.0/24; ← 封禁某网段 │
│ deny 1.2.3.4; ← 封禁单 IP │
│ allow all; │
│ } │
│ │
│ # WAF 基础规则(Nginx 层面防御) │
│ location / { │
│ # 阻止 SQL 注入 │
│ if ($query_string ~* "union.*select.*(") { │
│ return 403; │
│ } │
│ │
│ # 阻止路径遍历 │
│ if ($uri ~* "\.\./") { │
│ return 403; │
│ } │
│ │
│ # 限制请求方法 │
│ if ($request_method !~ ^(GET|POST|HEAD)$) { │
│ return 405; │
│ } │
│ │
│ # 隐藏 Nginx 版本号 │
│ server_tokens off; │
│ } │
│ │
│ 生产环境 WAF 建议: │
│ • Nginx 自带 if 规则 → 只适合简单场景 │
│ • 中型项目 → ModSecurity + Nginx(开源 WAF) │
│ • 云环境 → 云厂商 WAF(AWS WAF / 阿里云 WAF) │
│ • 不要试图用 Nginx 替代专业 WAF——规则维护成本太高 │
│ │
└─────────────────────────────────────────────────────────────┘第6部分:Nginx 变量与脚本
6.1 常用变量速查
┌─────────────────────────────────────────────────────────────┐
│ Nginx 核心变量 │
├─────────────────────────────────────────────────────────────┤
│ │
│ │ 变量 │ 含义 │ 示例值 │
│ ├───────────────────┼──────────────────┼────────────────┤
│ │ $host │ 请求的 Host 头 │ api.example.com │
│ ├───────────────────┼──────────────────┼────────────────┤
│ │ $uri │ 请求路径(不含参)│ /api/users │
│ ├───────────────────┼──────────────────┼────────────────┤
│ │ $request_uri │ 原始请求 URI │ /api/users?id=1 │
│ ├───────────────────┼──────────────────┼────────────────┤
│ │ $args / $query_ │ 查询参数字符串 │ id=1&name=foo │
│ │ string │ │ │
│ ├───────────────────┼──────────────────┼────────────────┤
│ │ $arg_id │ 指定参数的值 │ 1 │
│ ├───────────────────┼──────────────────┼────────────────┤
│ │ $remote_addr │ 客户端 IP │ 203.0.113.5 │
│ ├───────────────────┼──────────────────┼────────────────┤
│ │ $scheme │ http 或 https │ https │
│ ├───────────────────┼──────────────────┼────────────────┤
│ │ $request_method │ GET/POST/... │ POST │
│ ├───────────────────┼──────────────────┼────────────────┤
│ │ $server_name │ 匹配的 server_name│ example.com │
│ ├───────────────────┼──────────────────┼────────────────┤
│ │ $http_<header> │ 任意请求头 │ $http_user_agent│
│ ├───────────────────┼──────────────────┼────────────────┤
│ │ $status │ 响应状态码 │ 200 │
│ ├───────────────────┼──────────────────┼────────────────┤
│ │ $request_time │ 请求处理时间(秒)│ 0.123 │
│ ├───────────────────┼──────────────────┼────────────────┤
│ │ $upstream_addr │ 上游服务器地址 │ 10.0.1.1:3000 │
│ ├───────────────────┼──────────────────┼────────────────┤
│ │ $upstream_response│ 上游响应时间 │ 0.050 │
│ │ _time │ │ │
│ │
│ 登录日志配置: │
│ log_format detailed '$remote_addr - [$time_local] ' │
│ '"$request" $status $body_bytes_sent ' │
│ '"$http_referer" "$http_user_agent" ' │
│ '$request_time $upstream_addr ' │
│ '$upstream_response_time'; │
│ │
└─────────────────────────────────────────────────────────────┘6.2 set / map / if 脚本模式
┌─────────────────────────────────────────────────────────────┐
│ Nginx 脚本三剑客 │
├─────────────────────────────────────────────────────────────┤
│ │
│ set —— 设置变量: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ set $cache_key "$host$uri"; │ │
│ │ set $cache_valid "1"; │ │
│ │ if ($arg_nocache) { set $cache_valid "0"; } │ │
│ │ # set 只能在 server/location/if 中使用 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ map —— 映射转换(最推荐的条件逻辑): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ map $http_origin $cors_header { │ │
│ │ "https://example.com" "$http_origin"; │ │
│ │ "https://app.example.com" "$http_origin"; │ │
│ │ default ""; │ │
│ │ } │ │
│ │ # map 在 http 块定义,只在使用时才求值(惰性) │ │
│ │ # 比 if 更高效、更可预测 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ if —— 条件判断(谨慎使用): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ if ($http_user_agent ~* "bot") { │ │
│ │ return 403; │ │
│ │ } │ │
│ │ # if 是"邪恶"的(Igor Sysoev 原话) │ │
│ │ # 原因:if 在 location 中的行为是"重写"而非"分支" │ │
│ │ # 多个 if 可能导致意外行为 │ │
│ │ # 优先使用 map + try_files │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 推荐优先级:map > try_files > return/rewrite > if │
│ │
└─────────────────────────────────────────────────────────────┘第7部分:Nginx vs Caddy vs Traefik
7.1 三方对比
┌─────────────────────────────────────────────────────────────┐
│ Nginx vs Caddy vs Traefik │
├─────────────────────────────────────────────────────────────┤
│ │
│ │ 维度 │ Nginx │ Caddy │ Traefik │
│ ├──────────────┼─────────────┼─────────────┼──────────┤
│ │ 语言 │ C │ Go │ Go │
│ ├──────────────┼─────────────┼─────────────┼──────────┤
│ │ 配置方式 │ nginx.conf │ Caddyfile │ 标签+文件 │
│ │ │ (静态文件) │ (简洁 DSL) │ (K8s CRD) │
│ ├──────────────┼─────────────┼─────────────┼──────────┤
│ │ 自动 HTTPS │ ❌ 需手动 │ ✅ 零配置 │ ✅ 自动 │
│ │ │ certbot │ Let's Encrypt│ Let's Encrypt│
│ ├──────────────┼─────────────┼─────────────┼──────────┤
│ │ 动态配置 │ ❌ reload │ ✅ API │ ✅ 原生 │
│ ├──────────────┼─────────────┼─────────────┼──────────┤
│ │ 性能 │ ⭐⭐⭐⭐⭐ │ ⭐⭐⭐⭐ │ ⭐⭐⭐⭐ │
│ │ │ C 语言级别 │ Go 接近 C │ Go │
│ ├──────────────┼─────────────┼─────────────┼──────────┤
│ │ 内存占用 │ 极低 (~5MB) │ 低 (~30MB) │ 中等 │
│ ├──────────────┼─────────────┼─────────────┼──────────┤
│ │ Docker 友好 │ 中等 │ 高 │ 极高 │
│ ├──────────────┼─────────────┼─────────────┼──────────┤
│ │ K8s 原生 │ ❌ 需 Ingress│ ❌ │ ✅ CRD/ │
│ │ │ Controller │ │ IngressRoute│
│ ├──────────────┼─────────────┼─────────────┼──────────┤
│ │ 插件生态 │ 编译时模块 │ 编译时模块 │ 中间件插件 │
│ ├──────────────┼─────────────┼─────────────┼──────────┤
│ │ 适用场景 │ 高性能/稳定 │ 个人/中小 │ 容器/微服务│
│ │ │ 大型生产 │ 项目 │ K8s 环境 │
│ │
│ 选型建议: │
│ • 高流量/低延迟/极致性能 → Nginx │
│ • 个人项目/简单配置/快速上手 → Caddy │
│ • K8s 环境/动态配置/服务发现集成 → Traefik │
│ • 非 K8s 的微服务/API 管理 → Kong(基于 Nginx) │
│ │
└─────────────────────────────────────────────────────────────┘核心总结
总结1:Nginx 的性能来源于架构
Master/Worker + epoll + 非阻塞 I/O + 零拷贝(sendfile)。不是"C 语言快",而是事件驱动模型让一个 Worker 能处理成千上万个并发连接而不阻塞。
总结2:反向代理的核心配置是 upstream + proxy_pass + proxy_set_header
负载均衡算法选型、健康检查配置、请求头透传——这三个做好,反向代理就完成了 80% 的工作。
总结3:SSL 终止是生产环境的标配
不要在应用层处理 HTTPS。把证书放在 Nginx 上,后端用 HTTP,既简化证书管理,又提高应用性能。记得开 OCSP Stapling。
总结4:限流是防止系统崩溃的最后一道防线
limit_req(速率)和 limit_conn(连接数)是你的安全网。配置时要根据业务吞吐量定基线,留出足够的 burst 余量。
总结5:map 优于 if
Nginx 不是编程语言。尽量用 map 做条件映射,用 return 做快速响应,减少 if 的使用。if 在 location 中的行为不符合直觉。
章节测试
测试1:Nginx Master 进程的主要职责是什么?
测试2:以下负载均衡算法中,哪个最适合请求处理时间差异大的场景?
A. round-robin B. ip_hash C. least_conn D. random
测试3:SSL 终止(Termination)和 SSL 透传(Pass-through)的核心区别是什么?
测试4:Nginx 中 limit_req 的 burst 参数是什么意思?
测试5:为什么说 Nginx 的 map 指令比 if 指令更好?
测试6:对于 K8s 环境中的微服务网关,Nginx / Caddy / Traefik 如何选型?
参考答案
测试1答案
Nginx Master 进程的职责:
- 读取和验证 nginx.conf 配置文件
- 创建和管理 Worker 进程(fork 出多个 Worker)
- 处理信号:nginx -s reload(平滑重启)、nginx -s stop、nginx -s reopen(重新打开日志文件)
- 不处理任何客户端请求——所有请求由 Worker 处理
测试2答案
答案:C(least_conn)。least_conn 将请求转发给当前活跃连接数最少的后端服务器。当请求处理时间差异大时(如有些请求 10ms,有些 5s),轮询会导致某些服务器堆积长请求,而 least_conn 能自然地实现负载均衡。
测试3答案
SSL 终止:Nginx 在代理层解密 HTTPS,后端服务之间用 HTTP 通信。证书只在 Nginx 上配置,后端无需处理 TLS,内网流量明文传输。
SSL 透传:Nginx 在 L4 层(TCP)原样转发加密流量,不解密。每个后端需要配置自己的 TLS 证书。Nginx 无法检查 HTTP 内容(无法做路由、限流等 L7 操作)。
测试4答案
burst 是队列大小。当请求速率超过 rate 限制时,多余的请求不会立即被拒绝,而是排队等待。burst=20 表示最多允许 20 个请求排队。如果同时有 nodelay,队列中的请求也会立即处理(不等待),但当队列满时新请求返回 503。
测试5答案
- 声明性:map 是声明式映射,逻辑清晰,不易出错
- 惰性求值:map 只在使用变量时才求值,性能更好
- 可预测:if 在 location 中的行为是"重写"语义而非"分支"语义,多个 if 可能产生意外结果
- 作用域:map 在 http 块定义,全局可用,不受 location 上下文影响
测试6答案
- 一定要 Nginx:对性能/稳定性有极致要求(C10K 以上),团队对 Nginx 配置已经很熟悉
- 选 Caddy:简单项目,想要自动 HTTPS + 低维护成本
- 选 Traefik:K8s 环境,需要与服务发现(K8s Service/Ingress)原生集成,希望通过 CRD 声明式管理路由(IngressRoute)
相关笔记
- [[00-overview]] — 基础设施组件全景
- [[02-service-discovery]] — 服务发现(Consul/Nacos/DNS)
- [[03-configuration-center]] — 配置中心(Nacos/Apollo/Vault)
- [[../00-overview]] — 中间件全景总览
下一步学习
- [ ] 阅读 02 - 服务发现 — 理解服务发现的三种模式
- [ ] 在你的开发环境搭建一个 Nginx 反向代理,配置 SSL 和负载均衡
- [ ] 用
ab或wrk压测你的 Nginx 配置,观察性能表现 - [ ] 尝试用 Caddy 替代 Nginx 跑一个简单项目,感受配置差异
学习状态:🟡 开始学习