lolly/docs/nginx/04-nginx-proxy-loadbalancing.md
xfy cd807e43aa docs(nginx): 添加 nginx 源码架构分析文档
- Upstream 模块源码实现分析(负载均衡算法、故障转移)
- Stream 模块架构分析(TCP/UDP 代理处理流程)
- Mail 模块架构概述(IMAP/POP3/SMTP 协议支持)
- 事件模块源码架构(定时器、epoll、QUIC)
- 变量系统源码实现(索引变量、前缀变量机制)

基于 nginx 1.31.0 源码分析

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 11:09:26 +08:00

1699 lines
48 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# NGINX 反向代理与负载均衡指南
## 1. 反向代理基础
### 什么是反向代理
反向代理服务器接收客户端请求将请求转发给后端服务器获取响应后返回给客户端。NGINX 作为反向代理可以:
- 隐藏后端服务器真实地址
- 实现负载均衡
- 缓存响应内容
- SSL 终端加密
- 压缩响应内容
- 请求路由与重写
### 基础配置示例
```nginx
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend.example.com:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
```
---
## 2. proxy_pass 指令详解
### 语法
`proxy_pass URL;`
URL 可以是:
- HTTP 地址:`http://backend:8080`
- HTTPS 地址:`https://backend:8443`
- Unix Socket`unix:/tmp/backend.socket`
- upstream 组:`http://backend_group`
- 变量:`http://$backend`
### URI 传递规则
**带 URI 的 proxy_pass**:请求 URI 中匹配 location 的部分会被替换。
```nginx
location /name/ {
proxy_pass http://127.0.0.1/remote/;
# /name/test -> /remote/test
}
location /api/ {
proxy_pass http://backend/v1/;
# /api/users -> /v1/users
}
```
**不带 URI 的 proxy_pass**:请求 URI 以原始形式传递。
```nginx
location /some/path/ {
proxy_pass http://127.0.0.1;
# /some/path/test -> /some/path/test
}
```
**使用变量**
```nginx
location / {
proxy_pass http://$backend;
# 需要配合 resolver 指令解析域名
}
resolver 10.0.0.1 valid=300s;
```
---
## 3. 请求头设置
### proxy_set_header
设置传递给后端服务器的请求头。
```nginx
proxy_set_header Host $host; # 传递原始 Host
proxy_set_header Host $http_host; # 传递 Host 头(含端口)
proxy_set_header Host backend.example.com; # 固定 Host 值
proxy_set_header X-Real-IP $remote_addr; # 客户端真实 IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 代理链
proxy_set_header X-Forwarded-Proto $scheme; # 原始协议
# 删除请求头
proxy_set_header Accept-Encoding ""; # 删除该字段
```
### 默认行为
| 头字段 | 默认值 |
|--------|--------|
| `Host` | `$proxy_host`proxy_pass 中的地址) |
| `Connection` | `close` |
---
## 4. 负载均衡配置
### upstream 块定义
```nginx
upstream backend {
server backend1.example.com weight=5;
server backend2.example.com:8080;
server 192.168.0.1:8080 max_fails=3 fail_timeout=30s;
server backend3.example.com backup; # 备份服务器
server unix:/tmp/backend4;
}
server {
location / {
proxy_pass http://backend;
}
}
```
### 负载均衡算法
| 算法 | 指令 | 说明 |
|------|------|------|
| **轮询** | 默认 | 请求依次分发(加权) |
| **最少连接** | `least_conn;` | 分配给活动连接最少的服务器 |
| **IP Hash** | `ip_hash;` | 同一客户端 IP 始终路由到同一服务器 |
| **Hash** | `hash key [consistent];` | 基于指定键哈希,支持一致性哈希 |
| **随机** | `random [two [method]];` | 随机选择two 表示选两台再择优 |
### 配置示例
**轮询(默认)**
```nginx
upstream backend {
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
```
**加权轮询**
```nginx
upstream backend {
server srv1.example.com weight=5; # 5/7 的请求
server srv2.example.com weight=2; # 2/7 的请求
server srv3.example.com; # 1/7 的请求(默认 weight=1
}
```
**最少连接**
```nginx
upstream backend {
least_conn;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
```
**IP Hash会话持久性**
```nginx
upstream backend {
ip_hash;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
```
**一致性哈希**
```nginx
upstream backend {
hash $request_uri consistent;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
```
**随机负载均衡1.15.1+**
```nginx
upstream backend {
random; # 纯随机选择
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
# Power of Two Choices 算法(更智能)
upstream backend {
random two; # 随机选两台,按权重择优
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
# 结合最少连接策略
upstream backend {
random two least_conn; # 随机选两台,选连接数少的
server srv1.example.com;
server srv2.example.com;
}
```
**random 算法参数说明**
| 参数 | 说明 |
|------|------|
| `two` | 随机选择两台服务器,再根据策略择优 |
| `least_conn` | 与 `two` 配合,选择连接数较少的服务器 |
| `least_time=header` | 与 `two` 配合选择响应头时间最短的服务器NGINX Plus|
| `least_time=last_byte` | 与 `two` 配合选择完整响应时间最短的服务器NGINX Plus|
**适用场景**
- 多个负载均衡器共享后端时避免锁竞争
- 对一致性要求不高但需要低延迟的场景
- 配合 `zone` 实现无锁负载均衡
### server 指令参数
| 参数 | 说明 | 默认值 |
|------|------|--------|
| `weight=N` | 权重值 | 1 |
| `max_conns=N` | 最大并发连接数 | 0无限制 |
| `max_fails=N` | 失败次数阈值 | 1 |
| `fail_timeout=T` | 失败统计时间及不可用持续时间 | 10s |
| `backup` | 备份服务器(主服务器不可用时使用) | - |
| `down` | 标记为永久不可用 | - |
| `resolve` | 监控域名 IP 变化(需 zone + resolver | - |
```nginx
upstream backend {
zone backend 64k;
resolver 10.0.0.1;
server backend1.example.com weight=5 max_fails=3 fail_timeout=30s;
server backend2.example.com resolve;
server backup1.example.com backup;
}
```
---
## 5. 健康检查
### 被动健康检查(内置)
```nginx
upstream backend {
server srv1.example.com max_fails=3 fail_timeout=30s;
server srv2.example.com max_fails=3 fail_timeout=30s;
}
```
**机制**
-`fail_timeout` 时间内连续失败 `max_fails` 次,服务器标记为不可用
- `fail_timeout` 时间后再次尝试
### 主动健康检查NGINX Plus
```nginx
upstream backend {
zone backend 64k;
server srv1.example.com;
server srv2.example.com;
health_check interval=5s fails=3 passes=2;
health_check uri=/health;
}
```
---
## 6. 超时配置
### 主要超时指令
| 指令 | 说明 | 默认值 |
|------|------|--------|
| `proxy_connect_timeout` | 建立连接超时 | 60s |
| `proxy_send_timeout` | 传输请求超时 | 60s |
| `proxy_read_timeout` | 读取响应超时 | 60s |
```nginx
location / {
proxy_connect_timeout 5s;
proxy_send_timeout 10s;
proxy_read_timeout 30s;
proxy_pass http://backend;
}
```
---
## 7. 缓冲配置
### 响应缓冲
```nginx
proxy_buffering on; # 默认 on
proxy_buffer_size 4k; # 响应头缓冲区大小
proxy_buffers 8 16k; # 响应体缓冲区数量和大小
proxy_busy_buffers_size 32k; # 同时发送给客户端的缓冲区总大小
proxy_max_temp_file_size 1024m; # 临时文件最大大小
proxy_temp_file_write_size 64k; # 每次写入临时文件大小
```
### 禁用缓冲(实时传输)
```nginx
location /stream/ {
proxy_buffering off;
proxy_pass http://backend;
}
```
---
## 8. 缓存配置
### 缓存路径定义
```nginx
http {
proxy_cache_path /data/nginx/cache
levels=1:2 # 目录层级1:2 表示 16*256 个子目录)
keys_zone=one:10m # 共享内存区名称和大小1MB 约 8000 个键)
inactive=60m # 非活动数据保留时间
max_size=1g # 缓存最大大小
use_temp_path=off; # 临时文件存放位置
}
```
### 启用缓存
```nginx
server {
location / {
proxy_cache one; # 使用定义的缓存区
proxy_cache_key "$host$request_uri"; # 缓存键
proxy_cache_valid 200 302 10m; # 200/302 响应缓存 10 分钟
proxy_cache_valid 404 1m; # 404 响应缓存 1 分钟
proxy_cache_valid any 1m; # 其他响应缓存 1 分钟
proxy_pass http://backend;
}
}
```
### 缓存条件控制
```nginx
# 不从缓存获取响应的条件
proxy_cache_bypass $cookie_nocache $arg_nocache;
# 不将响应保存到缓存的条件
proxy_no_cache $http_pragma $http_authorization;
```
### 使用过期缓存
```nginx
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
# 在后端错误、超时、正在更新时使用过期缓存
```
### 缓存锁
```nginx
proxy_cache_lock on; # 同时刻只允许一个请求填充缓存
proxy_cache_lock_timeout 5s; # 锁超时时间
```
---
## 9. 故障转移
### proxy_next_upstream
定义在何种情况下将请求传递给下一台服务器。
```nginx
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_next_upstream_timeout 30s; # 限制总时间
proxy_next_upstream_tries 3; # 限制尝试次数
```
**条件类型**
| 条件 | 说明 |
|------|------|
| `error` | 与后端建立连接出错 |
| `timeout` | 连接、传输或读取超时 |
| `invalid_header` | 后端返回空或无效响应头 |
| `http_XXX` | 后端返回指定状态码 |
| `non_idempotent` | 非幂等请求POST、LOCK也进行重试 |
---
## 10. SSL/HTTPS 代理
### 代理到 HTTPS 后端
```nginx
location / {
proxy_pass https://backend.example.com;
proxy_ssl_verify on; # 验证后端证书
proxy_ssl_trusted_certificate /path/to/ca.crt;
proxy_ssl_verify_depth 2;
proxy_ssl_server_name on; # 启用 SNI
}
```
### 代理 SSL 配置
| 指令 | 说明 | 默认值 |
|------|------|--------|
| `proxy_ssl` | 启用 HTTPS 代理 | off |
| `proxy_ssl_protocols` | 启用的协议 | TLSv1.2 TLSv1.3 |
| `proxy_ssl_ciphers` | 加密套件 | DEFAULT |
| `proxy_ssl_verify` | 验证后端证书 | off |
| `proxy_ssl_verify_depth` | 验证深度 | 1 |
| `proxy_ssl_server_name` | 启用 SNI | off |
| `proxy_ssl_certificate` | 客户端证书 | - |
| `proxy_ssl_certificate_key` | 客户端密钥 | - |
---
## 11. WebSocket 代理
### 基础配置
```nginx
location /chat/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
```
### 动态处理
```nginx
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
location /chat/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s; # 增加超时时间
}
}
}
```
---
## 12. 高级代理指令
### proxy_bind
指定连接后端时使用的源地址用于多网卡服务器选择出口IP。
```nginx
语法: proxy_bind address [transparent];
默认:
上下文: http, server, location
```
```nginx
proxy_bind $server_addr; # 使用服务器IP
proxy_bind 192.168.1.1 transparent; # 透明代理需要root权限
```
### proxy_intercept_errors
拦截后端错误响应,配合 error_page 自定义错误页面。
```nginx
语法: proxy_intercept_errors on | off;
默认: off
上下文: http, server, location
```
```nginx
proxy_intercept_errors on;
error_page 500 502 503 504 /50x.html;
```
### proxy_hide_header / proxy_pass_header
控制后端响应头的传递行为:
```nginx
# 隐藏后端返回的特定头
proxy_hide_header X-Powered-By;
proxy_hide_header X-Runtime;
# 传递被默认隐藏的头
proxy_pass_header X-Accel-Redirect;
proxy_pass_header X-Accel-Limit-Rate;
```
### proxy_ignore_headers
忽略后端的特定响应头如缓存控制允许NGINX处理这些头
```nginx
proxy_ignore_headers Cache-Control Expires X-Accel-Redirect X-Accel-Expires;
```
### proxy_cookie_* 系列
修改后端返回的 Set-Cookie 头:
```nginx
# 修改域名
proxy_cookie_domain localhost example.com;
proxy_cookie_domain off; # 禁用域名修改
# 修改路径
proxy_cookie_path /foo/ /bar/;
proxy_cookie_path off; # 禁用路径修改
# 添加安全标志
proxy_cookie_flags session httponly secure samesite=strict;
proxy_cookie_flags * samesite=lax; # 应用到所有cookie
```
### proxy_limit_rate
限制从后端读取响应的传输速率:
```nginx
proxy_limit_rate 100k; # 100KB/s
```
### proxy_request_buffering
控制请求是否先完整缓冲再发送到后端:
```nginx
proxy_request_buffering on; # 默认,完整缓冲
proxy_request_buffering off; # 流式传输,支持上传进度
```
### proxy_redirect
修改后端返回的重定向头 Location 和 Refresh
```nginx
proxy_redirect default; # 使用默认替换
proxy_redirect off; # 禁用替换
proxy_redirect http://localhost:8080/ http://$host/; # 自定义替换
proxy_redirect ~^http://([^/]+)/(.+)$ http://$host/$2; # 使用正则
```
---
## 13. SSL 客户端证书认证 (proxy_ssl_*)
用于 mTLS 双向认证场景NGINX 作为客户端向后端提供证书:
```nginx
location / {
proxy_pass https://backend.example.com;
# mTLS 双向认证
proxy_ssl_certificate /path/to/client.crt;
proxy_ssl_certificate_key /path/to/client.key;
# 验证后端证书
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /path/to/ca.crt;
proxy_ssl_verify_depth 2;
# SSL协议和加密套件
proxy_ssl_protocols TLSv1.2 TLSv1.3;
proxy_ssl_ciphers HIGH:!aNULL;
# 会话复用
proxy_ssl_session_reuse on;
# SNI支持
proxy_ssl_server_name on;
proxy_ssl_name backend.example.com;
}
```
| 指令 | 说明 | 默认值 |
|------|------|--------|
| `proxy_ssl_certificate` | 客户端证书路径 | — |
| `proxy_ssl_certificate_key` | 客户端私钥路径 | — |
| `proxy_ssl_verify` | 验证后端证书 | off |
| `proxy_ssl_trusted_certificate` | 受信任CA证书 | — |
| `proxy_ssl_verify_depth` | 验证深度 | 1 |
| `proxy_ssl_protocols` | 启用的协议 | TLSv1.2 TLSv1.3 |
| `proxy_ssl_ciphers` | 加密套件 | DEFAULT |
| `proxy_ssl_session_reuse` | 会话复用 | on |
| `proxy_ssl_name` | SNI名称 | — |
---
## 14. 高级缓存指令
### proxy_cache_methods
指定可缓存的请求方法:
```nginx
proxy_cache_methods GET HEAD POST; # 可缓存 POST 请求
```
### proxy_cache_min_uses
设置最小访问次数才开始缓存,避免缓存低频请求:
```nginx
proxy_cache_min_uses 3; # 第3次访问才开始缓存
```
### proxy_cache_background_update
后台异步更新过期缓存(类似 stale-while-revalidate
```nginx
proxy_cache_background_update on;
```
### proxy_cache_revalidate
使用 If-Modified-Since 和 If-None-Match 重新验证缓存:
```nginx
proxy_cache_revalidate on; # 减少数据传输
```
### proxy_cache_convert_head
自动将 HEAD 请求转为 GET 以获取响应体:
```nginx
proxy_cache_convert_head on; # 默认启用
```
### proxy_cache_purge
支持 PURGE 方法清除缓存(需编译时启用):
```nginx
location ~ /purge(/.*) {
proxy_cache_purge cache_zone $1;
}
```
---
## 15. FastCGI 代理
### 基础配置
```nginx
location ~ \.php$ {
fastcgi_pass localhost:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
```
### FastCGI 指令完整列表
| 指令 | 语法 | 默认值 | 上下文 |
|------|------|--------|--------|
| `fastcgi_pass` | fastcgi_pass address; | — | location |
| `fastcgi_index` | fastcgi_index name; | — | http, server, location |
| `fastcgi_param` | fastcgi_param parameter value [if_not_empty]; | — | http, server, location |
| `fastcgi_split_path_info` | fastcgi_split_path_info regex; | — | location |
| `fastcgi_buffer_size` | fastcgi_buffer_size size; | 4k/8k | http, server, location |
| `fastcgi_buffers` | fastcgi_buffers number size; | 8 4k/8k | http, server, location |
| `fastcgi_busy_buffers_size` | fastcgi_busy_buffers_size size; | 8k/16k | http, server, location |
| `fastcgi_temp_file_write_size` | fastcgi_temp_file_write_size size; | 8k/16k | http, server, location |
| `fastcgi_temp_path` | fastcgi_temp_path path [level1 [level2 [level3]]]; | — | http, server, location |
| `fastcgi_cache` | fastcgi_cache zone; | — | http, server, location |
| `fastcgi_cache_key` | fastcgi_cache_key string; | — | http, server, location |
| `fastcgi_cache_valid` | fastcgi_cache_valid [code...] time; | — | http, server, location |
| `fastcgi_cache_methods` | fastcgi_cache_methods method...; | GET HEAD | http, server, location |
| `fastcgi_cache_min_uses` | fastcgi_cache_min_uses number; | 1 | http, server, location |
| `fastcgi_cache_bypass` | fastcgi_cache_bypass string...; | — | http, server, location |
| `fastcgi_no_cache` | fastcgi_no_cache string...; | — | http, server, location |
| `fastcgi_cache_use_stale` | fastcgi_cache_use_stale condition...; | — | http, server, location |
| `fastcgi_cache_background_update` | fastcgi_cache_background_update on/off; | off | http, server, location |
| `fastcgi_cache_revalidate` | fastcgi_cache_revalidate on/off; | off | http, server, location |
| `fastcgi_cache_lock` | fastcgi_cache_lock on/off; | off | http, server, location |
| `fastcgi_cache_lock_timeout` | fastcgi_cache_lock_timeout time; | 5s | http, server, location |
| `fastcgi_cache_convert_head` | fastcgi_cache_convert_head on/off; | on | http, server, location |
| `fastcgi_connect_timeout` | fastcgi_connect_timeout time; | 60s | http, server, location |
| `fastcgi_send_timeout` | fastcgi_send_timeout time; | 60s | http, server, location |
| `fastcgi_read_timeout` | fastcgi_read_timeout time; | 60s | http, server, location |
| `fastcgi_send_lowat` | fastcgi_send_lowat size; | 0 | http, server, location |
| `fastcgi_request_buffering` | fastcgi_request_buffering on/off; | on | http, server, location |
| `fastcgi_intercept_errors` | fastcgi_intercept_errors on/off; | off | http, server, location |
| `fastcgi_hide_header` | fastcgi_hide_header field; | — | http, server, location |
| `fastcgi_pass_header` | fastcgi_pass_header field; | — | http, server, location |
| `fastcgi_ignore_headers` | fastcgi_ignore_headers field...; | — | http, server, location |
| `fastcgi_limit_rate` | fastcgi_limit_rate rate; | 0 | http, server, location |
### FastCGI 缓存完整配置示例
```nginx
http {
# 缓存路径定义
fastcgi_cache_path /var/cache/nginx/php
levels=1:2
keys_zone=php:10m
max_size=100m
inactive=60m
use_temp_path=off;
server {
listen 80;
server_name example.com;
location ~ \.php$ {
# 启用缓存
fastcgi_cache php;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
# 缓存有效期
fastcgi_cache_valid 200 302 1h;
fastcgi_cache_valid 404 1m;
fastcgi_cache_valid any 5m;
# 使用过期缓存
fastcgi_cache_use_stale error timeout updating http_500 http_503;
# 后台更新
fastcgi_cache_background_update on;
# 重新验证
fastcgi_cache_revalidate on;
# 缓存锁
fastcgi_cache_lock on;
fastcgi_cache_lock_timeout 5s;
# 绕过缓存条件
fastcgi_cache_bypass $cookie_nocache $arg_nocache;
fastcgi_no_cache $http_pragma $http_authorization;
# FastCGI 后端
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# 超时设置
fastcgi_connect_timeout 5s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 60s;
# 缓冲配置
fastcgi_buffer_size 16k;
fastcgi_buffers 8 16k;
fastcgi_busy_buffers_size 32k;
# 错误处理
fastcgi_intercept_errors on;
include fastcgi_params;
}
}
}
```
---
## 16. 内置变量
### 代理相关变量
| 变量 | 说明 |
|------|------|
| `$proxy_host` | proxy_pass 中的服务器名称和端口 |
| `$proxy_port` | proxy_pass 中的端口 |
| `$proxy_add_x_forwarded_for` | X-Forwarded-For 头 + 客户端 IP |
### Upstream 响应时间变量(用于性能监控)
| 变量 | 说明 | 单位 |
|------|------|------|
| `$upstream_addr` | 上游服务器地址IP:端口)| - |
| `$upstream_connect_time` | 与上游建立连接的时间(含 SSL 握手)| 秒 |
| `$upstream_header_time` | 接收到上游响应头的时间 | 秒 |
| `$upstream_response_time` | 完整响应时间(从建立连接到接收完成)| 秒 |
| `$upstream_response_length` | 上游响应体长度 | 字节 |
| `$upstream_bytes_received` | 从上游接收的总字节数 | 字节 |
| `$upstream_bytes_sent` | 发送到上游的总字节数 | 字节 |
| `$upstream_status` | 上游返回的 HTTP 状态码 | - |
| `$upstream_cache_status` | 缓存命中状态HIT/MISS/EXPIRED 等)| - |
| `$upstream_queue_time` | 请求在队列中等待的时间NGINX Plus| 秒 |
**日志格式中使用响应时间变量**
```nginx
log_format detailed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time '
'uct="$upstream_connect_time" '
'uht="$upstream_header_time" '
'urt="$upstream_response_time" '
'upstream=$upstream_addr '
'upstream_status=$upstream_status '
'upstream_bytes=$upstream_response_length';
access_log /var/log/nginx/access.log detailed;
```
**响应时间变量解读**
```
请求时间线:
客户端 ──▶ NGINX ──▶ 连接上游 ──▶ 发送请求 ──▶ 接收响应头 ──▶ 接收响应体 ──▶ 客户端
│ │ │ │ │
│ │ │ │ │
└───────────┴──────────────┴────────────────┴────────────────┘
│ │ │
$upstream_ $upstream_ $upstream_
connect_time header_time response_time
```
- `$upstream_connect_time`TCP 连接 + SSL 握手时间
- `$upstream_header_time`:从开始到收到响应头
- `$upstream_response_time`:完整请求处理时间
- `$request_time`:从客户端发起请求到响应完成(包含所有上游)
**基于响应时间的告警配置示例**
```nginx
# 慢请求日志
map $upstream_response_time $slow_log {
default 0;
"~^[2-9]\." 1; # 2秒以上
"~^[0-9]{2,}" 1; # 10秒以上
}
server {
location /api/ {
# 记录慢请求
access_log /var/log/nginx/slow.log detailed if=$slow_log;
proxy_pass http://backend;
}
}
```
---
## 17. 综合配置示例
```nginx
http {
upstream backend {
zone backend 64k;
least_conn;
server backend1.example.com weight=5 max_fails=3 fail_timeout=30s;
server backend2.example.com resolve;
server backup.example.com backup;
keepalive 32;
}
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=main:10m inactive=60m max_size=1g;
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
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_connect_timeout 5s;
proxy_read_timeout 30s;
proxy_cache main;
proxy_cache_key "$host$request_uri";
proxy_cache_valid 200 10m;
proxy_next_upstream error timeout http_502 http_503;
}
location /api/ {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_buffering off;
}
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
}
}
}
```
---
## 18. 主动健康检查详解
### 18.1 被动检查 vs 主动检查
| 特性 | 被动健康检查 (Passive) | 主动健康检查 (Active) |
|------|----------------------|---------------------|
| **实现方式** | 基于真实客户端请求响应判断 | 独立的探测请求周期性检测 |
| **触发时机** | 实际请求失败时 | 按配置间隔主动发起 |
| **资源占用** | 无额外开销 | 需要额外的连接和请求 |
| **发现速度** | 慢(依赖真实流量) | 快(独立探测) |
| **可用性** | 开源 NGINX 内置 | NGINX Plus 商业版 / 第三方模块 |
| **配置位置** | `server` 指令参数 | `upstream` 块或 `location` 指令 |
| **典型参数** | `max_fails`, `fail_timeout` | `interval`, `fails`, `passes`, `match` |
**被动检查机制**
```nginx
upstream backend {
# 在 fail_timeout(30s) 内连续失败 max_fails(3) 次,标记为不可用
server srv1.example.com max_fails=3 fail_timeout=30s;
}
```
**主动检查优势**
- 不依赖真实客户端流量即可检测后端状态
- 可以检测特定的健康检查端点(如 `/health`
- 支持自定义匹配规则验证响应内容
- 支持 gRPC、TCP、UDP 等多种协议
### 18.2 HTTP 健康检查指令详解 (NGINX Plus)
**注意**HTTP 主动健康检查模块 (`ngx_http_upstream_hc_module`) 是 NGINX Plus 商业订阅的一部分。
#### health_check 指令
**语法**`health_check [parameters];`
**上下文**`location`
**功能**:启用 upstream 服务器组的定期健康检查
**参数说明**
| 参数 | 语法 | 默认值 | 说明 |
|------|------|--------|------|
| `interval` | `interval=time` | `5s` | 检查间隔时间 |
| `jitter` | `jitter=time` | — | 随机延迟时间,避免多个服务器同时检查 |
| `fails` | `fails=number` | `1` | 连续失败次数判定为不健康 |
| `passes` | `passes=number` | `1` | 连续成功次数判定为健康 |
| `uri` | `uri=uri` | `/` | 健康检查请求的 URI |
| `port` | `port=number` | 服务器端口 | 健康检查使用的端口 |
| `match` | `match=name` | — | 引用 `match` 块进行响应验证 |
| `mandatory` | `mandatory [persistent]` | — | 初始状态为 "checking"`persistent` 在 reload 后保持状态 |
| `keepalive_time` | `keepalive_time=time` | — | 启用健康检查连接的 keepalive |
| `type=grpc` | `type=grpc [grpc_service=name] [grpc_status=code]` | — | 启用 gRPC 健康检查 |
#### match 指令
**语法**`match name { ... }`
**上下文**`http`
**功能**:定义响应验证测试集
**测试项**
| 测试项 | 语法 | 说明 |
|--------|------|------|
| `status` | `status [!] code [code...]` | 状态码匹配,支持范围如 `200-399` |
| `header` | `header header [operator] value` | 响应头匹配,`=` 精确匹配,`~` 正则匹配 |
| `body` | `body ~ "regex"` | 响应体正则匹配(只检查前 256KB |
| `require` | `require $variable` | 变量非空且不为 "0" |
**header 操作符**
- `=``==`:精确相等
- `!=`:不相等
- `~`:正则匹配(区分大小写)
- `~*`:正则匹配(不区分大小写)
#### 配置示例
**基础健康检查**
```nginx
upstream dynamic {
zone upstream_dynamic 64k; # 共享内存区必须
server backend1.example.com weight=5;
server backend2.example.com:8080 fail_timeout=5s slow_start=30s;
}
server {
location / {
proxy_pass http://dynamic;
health_check; # 使用默认配置
}
}
```
**高级配置**
```nginx
server {
location / {
proxy_pass http://backend;
health_check interval=10s jitter=2s fails=3 passes=2
uri=/health port=8080 match=server_ok
keepalive_time=60s;
}
}
match server_ok {
status 200; # 状态码必须是 200
header Content-Type = application/json; # Content-Type 精确匹配
header X-Health-Status ~ ^ok$; # 正则匹配头值
body ~ "\"status\":\\s*\"healthy\""; # 响应体包含状态标记
}
```
**gRPC 健康检查**(不兼容 `uri``match`
```nginx
upstream grpc_backend {
zone grpc_zone 64k;
server grpc1.example.com:50051;
server grpc2.example.com:50051;
}
server {
location / {
grpc_pass grpc://grpc_backend;
health_check mandatory type=grpc grpc_service=myapp.HealthCheck grpc_status=12;
}
}
```
### 18.3 Stream 健康检查指令详解 (NGINX Plus)
**注意**Stream 主动健康检查模块 (`ngx_stream_upstream_hc_module`) 是 NGINX Plus 商业订阅的一部分。
#### 指令概览
| 指令 | 上下文 | 默认值 | 说明 |
|------|--------|--------|------|
| `health_check` | `server` | — | 启用健康检查 |
| `health_check_timeout` | `stream`, `server` | `5s` | 健康检查超时 |
| `match` | `stream` | — | 定义响应验证规则 |
#### health_check 参数Stream
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `interval` | `5s` | 检查间隔 |
| `jitter` | — | 随机延迟 |
| `fails` | `1` | 失败次数阈值 |
| `passes` | `1` | 成功次数阈值 |
| `match` | — | 引用 match 块 |
| `port` | 服务器端口 | 检查端口 |
| `udp` | — | 使用 UDP 协议 |
| `mandatory` | — | 初始状态为 "checking" |
| `persistent` | — | reload 后保持状态 |
#### match 块Stream
| 测试项 | 语法 | 说明 |
|--------|------|------|
| `send` | `send "string"` | 发送给服务器的字符串(支持 `\x` 十六进制) |
| `expect` | `expect "string"` / `expect ~ "regex"` | 期望的响应 |
**注意**:只检查服务器返回数据的前 `proxy_buffer_size` 字节。
#### 配置示例
**TCP 基础检查**
```nginx
upstream tcp_backend {
zone tcp_zone 64k;
server backend1.example.com:12345 weight=5;
server backend2.example.com:12345;
}
server {
listen 12346;
proxy_pass tcp_backend;
health_check interval=5s;
}
```
**UDP 健康检查**
```nginx
upstream dns_upstream {
zone dns_zone 64k;
server dns1.example.com:53;
}
server {
listen 53 udp;
proxy_pass dns_upstream;
health_check udp interval=3s; # 发送探测并期望无 ICMP 不可达回复
}
```
**自定义匹配规则MySQL 检查)**
```nginx
upstream mysql_backend {
zone mysql_zone 10m;
server db1.example.com:3306;
server db2.example.com:3306;
}
match mysql_handshake {
# 发送 MySQL 握手包(十六进制)
send "\x3a\x00\x00\x01\x0a\x35\x2e\x35\x2e\x32\x2d\x6d\x32\x00\x01...";
# 期望收到包含版本信息的响应
expect ~ "\x4a\x00\x00\x00\x0a";
}
server {
listen 3307;
proxy_pass mysql_backend;
health_check match=mysql_handshake interval=5s;
health_check_timeout 10s;
}
```
**HTTP 风格的 TCP 检查**
```nginx
match http_check {
send "GET /health HTTP/1.0\r\nHost: localhost\r\n\r\n";
expect ~ "200 OK";
}
server {
listen 80;
proxy_pass backend;
health_check match=http_check interval=5s fails=3 passes=2;
}
```
### 18.4 自定义健康检查配置示例
#### 场景一API 网关健康检查
```nginx
http {
upstream api_backend {
zone api_zone 64k;
server api1.example.com:8080;
server api2.example.com:8080;
server api3.example.com:8080;
}
# 健康检查匹配规则
match api_healthy {
status 200;
header Content-Type = application/json;
body ~ "\"status\":\\s*\"up\"";
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://api_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 健康检查配置
health_check interval=5s jitter=1s fails=3 passes=2
uri=/api/health
match=api_healthy;
}
# 健康检查状态页NGINX Plus
location /upstream_status {
upstream_status;
access_log off;
allow 10.0.0.0/8;
deny all;
}
}
}
```
#### 场景二:多协议混合检查
```nginx
# TCP 服务健康检查
stream {
upstream redis_backend {
zone redis_zone 64k;
server redis1.example.com:6379;
server redis2.example.com:6379;
}
match redis_ping {
send "PING\r\n";
expect ~ "\+PONG";
}
server {
listen 6379;
proxy_pass redis_backend;
health_check match=redis_ping interval=10s fails=2 passes=2;
health_check_timeout 3s;
}
}
# HTTP 服务健康检查
http {
upstream web_backend {
zone web_zone 64k;
server web1.example.com:80;
server web2.example.com:80;
}
server {
location / {
proxy_pass http://web_backend;
health_check interval=5s uri=/nginx_health;
}
}
}
```
#### 场景三:微服务 gRPC 健康检查
```nginx
upstream grpc_services {
zone grpc_zone 64k;
server service1.example.com:50051;
server service2.example.com:50051;
}
server {
listen 50051 http2;
location / {
grpc_pass grpc://grpc_services;
# gRPC 健康检查:使用标准 gRPC Health Checking Protocol
# grpc_status=12 (UNIMPLEMENTED) 表示服务未实现健康检查接口
# grpc_status=0 (OK) 表示服务健康
health_check mandatory type=grpc grpc_service=grpc.health.v1.Health
interval=5s fails=3 passes=2;
}
}
```
### 18.5 健康检查与负载均衡配合
#### 状态流转机制
```
初始状态
|
v
┌───────────┐ ┌──────────────┐
│ checking │────▶│ unhealthy │
└─────┬─────┘ └──────────────┘
│ │
│ passes 次成功 │ fails 次失败
v v
┌───────────┐ ┌──────────────┐
│ healthy │◀────│ │
└───────────┘ └──────────────┘
```
**关键行为**
- `checking` 状态:初始或 reload 后,不接收客户端请求
- `mandatory` 参数:强制等待首次健康检查完成才标记为健康
- `persistent` 参数reload 后如之前是健康状态则保持 healthy
#### 与负载均衡算法结合
```nginx
upstream backend {
zone backend 64k;
least_conn; # 最少连接算法
server srv1.example.com weight=5;
server srv2.example.com;
server srv3.example.com;
# 被动检查参数与主动检查并存
# 被动检查作为兜底,主动检查提供快速发现
}
server {
location / {
proxy_pass http://backend;
health_check interval=5s fails=3 passes=2;
# 故障转移配置
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_timeout 5s;
proxy_next_upstream_tries 2;
}
}
```
**监控指标集成**
```nginx
log_format health_log '$remote_addr - $remote_user [$time_local] '
'"$request" $status '
'upstream=$upstream_addr '
'upstream_status=$upstream_status '
'health_check=$upstream_health_check_status';
server {
location / {
proxy_pass http://backend;
health_check interval=5s;
access_log /var/log/nginx/health.log health_log;
}
}
```
### 18.6 开源 NGINX 的替代方案
由于主动健康检查是 NGINX Plus 商业特性,开源版本需要使用第三方模块。
#### nginx_upstream_check_module (Tengine)
由阿里巴巴 Tengine 团队开发的第三方模块,支持主动健康检查。
**源码地址**https://github.com/yaoweibin/nginx_upstream_check_module
**安装方法**
```bash
# 下载模块源码
git clone https://github.com/yaoweibin/nginx_upstream_check_module.git
# 下载 NGINX 源码并解压
cd /usr/local/src
tar -xzvf nginx-1.24.0.tar.gz
cd nginx-1.24.0
# 应用补丁(根据版本选择)
patch -p1 < /path/to/nginx_upstream_check_module/check.patch
# 或 patch -p1 < /path/to/nginx_upstream_check_module/check_1.20.1+.patch
# 编译安装
./configure \
--prefix=/etc/nginx \
--add-module=/path/to/nginx_upstream_check_module \
--with-http_ssl_module \
--with-http_v2_module
make && make install
```
**指令说明**
| 指令 | 语法 | 默认值 | 说明 |
|------|------|--------|------|
| `check` | `check interval=ms [fall=N] [rise=N] [timeout=ms] [default_down=true\|false] [type=tcp\|http\|ssl_hello\|mysql\|ajp\|fastcgi]` | 见右侧 | 启用健康检查<br>`interval`: 检查间隔(ms)<br>`fall`: 失败次数<br>`rise`: 成功次数<br>`timeout`: 超时(ms)<br>`default_down`: 默认下线状态<br>`type`: 检查协议 |
| `check_keepalive_requests` | `check_keepalive_requests num` | `1` | 长连接检查次数 |
| `check_http_send` | `check_http_send "packet"` | `GET / HTTP/1.0\r\n\r\n` | HTTP 检查请求包 |
| `check_http_expect_alive` | `check_http_expect_alive [http_2xx] [http_3xx] [http_4xx] [http_5xx]` | `http_2xx` `http_3xx` | 视为健康的 HTTP 状态码 |
| `check_fastcgi_param` | `check_fastcgi_param parameter value` | — | FastCGI 检查参数 |
| `check_status` | `check_status [html\|csv\|json]` | `html` | 状态查看页面格式 |
**配置示例**
```nginx
upstream backend {
server 192.168.0.1:80;
server 192.168.0.2:80;
# 每 5 秒检查一次,失败 3 次下线,成功 2 次上线,超时 4 秒
check interval=5000 rise=2 fall=3 timeout=4000 type=http;
# HTTP 健康检查配置
check_http_send "GET /health HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
# 启用长连接检查(可选)
check_keepalive_requests 100;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
# 健康检查状态页
location /status {
check_status json; # 可选 html, csv, json
access_log off;
allow 10.0.0.0/8;
deny all;
}
}
```
**支持的健康检查类型**
- `tcp`:仅建立 TCP 连接
- `http`:发送 HTTP 请求并验证响应
- `ssl_hello`:发送 SSL Client Hello
- `mysql`:发送 MySQL ping 包
- `ajp`:发送 AJP ping 包
- `fastcgi`:发送 FastCGI 请求
#### 其他替代方案对比
| 方案 | 类型 | 活跃度 | 特点 |
|------|------|--------|------|
| **nginx_upstream_check_module** | 第三方模块 | 中等 | 功能完整Tengine 使用 |
| **nginx-upsync-module** | 第三方模块 | 低 | 结合 Consul/etcd 动态发现 |
| **OpenResty + lua-resty-healthcheck** | Lua 扩展 | 高 | 灵活可编程 |
| **Traefik** | 替代代理 | 高 | 原生支持主动健康检查 |
| **Envoy** | 替代代理 | 高 | 云原生,功能强大 |
#### OpenResty + Lua 实现示例
```nginx
lua_shared_dict healthcheck 1m;
upstream backend {
server 127.0.0.1:8081;
server 127.0.0.1:8082;
server 127.0.0.1:8083;
}
init_worker_by_lua_block {
local hc = require "resty.healthcheck"
local checker = hc.new({
name = "my-checker",
shm_name = "healthcheck",
checks = {
active = {
healthy = {
interval = 5,
successes = 2,
},
unhealthy = {
interval = 5,
http_failures = 3,
},
},
},
})
checker:add_target("127.0.0.1", 8081)
checker:add_target("127.0.0.1", 8082)
checker:add_target("127.0.0.1", 8083)
}
```
**选型建议**
- 商业环境且有预算:**NGINX Plus**(完整支持,商业支持)
- 开源替代且功能优先:**nginx_upstream_check_module**
- 需要动态配置:**OpenResty + lua-resty-upstream-healthcheck**
- 新架构选型:**Traefik** 或 **Envoy**(原生支持服务发现)
---
## 19. Upstream 模块源码实现分析
基于 nginx 1.31.0 源码(`lib/nginx/src/http/`)。
### 19.1 核心数据结构
#### ngx_http_upstream_t - 上游请求结构
```c
// src/http/ngx_http_upstream.h:336-420
struct ngx_http_upstream_s {
ngx_http_upstream_handler_pt read_event_handler;
ngx_http_upstream_handler_pt write_event_handler;
ngx_peer_connection_t peer; // 对端连接
ngx_event_pipe_t *pipe; // 管道(流式传输)
ngx_chain_t *request_bufs; // 请求缓冲
ngx_http_upstream_conf_t *conf; // upstream 配置
ngx_http_upstream_srv_conf_t *upstream; // upstream 组配置
ngx_http_upstream_headers_in_t headers_in; // 上游响应头
ngx_buf_t buffer; // 响应缓冲
// 回调函数
ngx_int_t (*create_request)(ngx_http_request_t *r);
ngx_int_t (*reinit_request)(ngx_http_request_t *r);
ngx_int_t (*process_header)(ngx_http_request_t *r);
void (*finalize_request)(ngx_http_request_t *r, ngx_int_t rc);
ngx_msec_t start_time;
ngx_http_upstream_state_t *state; // 状态信息
unsigned store:1;
unsigned cacheable:1;
unsigned buffering:1;
unsigned keepalive:1;
unsigned upgrade:1; // WebSocket 升级
};
```
#### ngx_http_upstream_rr_peer_t - 服务器节点
```c
// src/http/ngx_http_upstream_round_robin.h
struct ngx_http_upstream_rr_peer_s {
ngx_str_t name; // 服务器名称
ngx_addr_t *addrs; // 地址数组
ngx_uint_t naddrs; // 地址数
ngx_uint_t weight; // 配置权重
ngx_uint_t effective_weight; // 有效权重(故障恢复)
ngx_uint_t current_weight; // 当前权重(轮询用)
ngx_uint_t max_conns; // 最大连接数
ngx_uint_t conns; // 当前连接数
ngx_uint_t max_fails; // 最大失败次数
time_t fail_timeout; // 失败超时
time_t accessed; // 最后访问时间
time_t checked; // 最后检查时间
ngx_uint_t fails; // 失败次数
ngx_uint_t down; // 是否下线
unsigned backup:1; // 是否备份服务器
};
```
### 19.2 加权轮询算法(平滑分配)
```c
// src/http/ngx_http_upstream_round_robin.c:810-895
ngx_http_upstream_get_peer(ngx_peer_connection_t *pc, void *data)
{
// 平滑加权轮询:避免突发集中分配
for (p = peers->peer; p; p++) {
if (p 已尝试过 || p down || 超过 max_fails || 超过 max_conns)
continue;
p->current_weight += p->effective_weight; // 累加有效权重
total += p->effective_weight;
// 慢恢复:失败后逐步恢复权重
if (p->effective_weight < p->weight)
p->effective_weight++;
// 选 current_weight 最高的
if (best == NULL || p->current_weight > best->current_weight)
best = p;
}
// 选中后扣减总权重
best->current_weight -= total;
return best;
}
```
**算法特点**
- 权重平滑分配,避免请求集中到某台服务器
- 故障恢复时权重逐步回升,而不是直接恢复
- 支持 backup 服务器组切换
### 19.3 一致性哈希实现
```c
// src/http/modules/ngx_http_upstream_hash_module.c
typedef struct {
uint32_t hash;
ngx_str_t *server;
} ngx_http_upstream_chash_point_t;
typedef struct {
ngx_uint_t number;
ngx_http_upstream_chash_point_t point[1]; // 虚拟节点数组
} ngx_http_upstream_chash_points_t;
// 虚拟节点分布在 0-2^32 的圆环上
// 根据 key 的 hash 值查找最近的节点(二分查找)
ngx_http_upstream_find_chash_point(points, hash);
```
### 19.4 Upstream 请求处理流程
```
ngx_http_upstream_init(r)
|
v
ngx_http_upstream_init_request(r)
|
+-- 解析 upstream 名称
+-- 创建 peer 连接 (peer.get = ngx_http_upstream_get_round_robin_peer)
|
v
ngx_http_upstream_connect(r, u)
|
+-- ngx_event_connect_peer() 连接到上游服务器
|
v
ngx_http_upstream_send_request() --> 发送客户端请求到上游
|
v
ngx_http_upstream_process_header(r, u)
|
+-- 解析上游响应头
+-- ngx_http_upstream_process_headers() 处理 set-cookie 等
|
v
分支:
+-- 缓冲模式: ngx_http_upstream_process_request()
| (读完整上游响应到临时文件/内存,再转发给客户端)
|
+-- 非缓冲模式: ngx_http_upstream_process_upgraded()
(双向透传,用于 WebSocket 等升级连接)
|
v
ngx_http_upstream_finalize_request(r, u, rc)
|
+-- peer.free() 记录失败/成功,更新权重
+-- 清理资源
```
### 19.5 故障转移机制
```c
// src/http/ngx_http_upstream.c
#define NGX_HTTP_UPSTREAM_FT_ERROR 0x00000002
#define NGX_HTTP_UPSTREAM_FT_TIMEOUT 0x00000004
#define NGX_HTTP_UPSTREAM_FT_INVALID_HEADER 0x00000008
#define NGX_HTTP_UPSTREAM_FT_HTTP_500 0x00000010
#define NGX_HTTP_UPSTREAM_FT_HTTP_502 0x00000020
#define NGX_HTTP_UPSTREAM_FT_HTTP_503 0x00000040
#define NGX_HTTP_UPSTREAM_FT_HTTP_504 0x00000080
ngx_http_upstream_next(r, u, ft_type)
{
if (u->peer.tries > u->conf->next_upstream_tries)
return finalize;
if (ft_type & u->conf->next_upstream) {
// 符合重试条件,选择下一台服务器
ngx_http_upstream_connect(r, u);
} else {
// 不符合条件,直接结束请求
ngx_http_upstream_finalize_request(r, u, status);
}
}
```
### 19.6 关键源码路径
| 功能 | 文件路径 | 大小 |
|------|----------|------|
| Upstream 核心 | `src/http/ngx_http_upstream.c` | 187KB |
| Upstream 头文件 | `src/http/ngx_http_upstream.h` | 16KB |
| Round Robin 负载均衡 | `src/http/ngx_http_upstream_round_robin.c` | 32KB |
| Least Connections | `src/http/modules/ngx_http_upstream_least_conn_module.c` | 4KB |
| Hash/一致性哈希 | `src/http/modules/ngx_http_upstream_hash_module.c` | 8KB |
| Random 负载均衡 | `src/http/modules/ngx_http_upstream_random_module.c` | 6KB |
| Proxy 模块 | `src/http/modules/ngx_http_proxy_module.c` | 80KB |
---
*源码分析基于 nginx 1.31.0*
*源码目录:`lib/nginx/src/http/`*