【第三章】14-常用模块2-ngx_http_proxy_module
什么是代理?代理是作为中间层,作用于上下游两端。将下游的“请求”转移提交到上游,将上游的“响应”提交给下游。代理不仅在设计模式等程序设计中有比较广泛的使用,同时在大型的系统工程中使用也比较广泛,在web服务器领域,代理出现的频率也是很高的。
目录
一、 ngx_http_proxy_module模块介绍
1.1 代理的本质与应用场景:从中间层到工程实践
1.1.1 重新定义“代理”:不只是桥梁
1.1.2 正向代理 vs 反向代理:穿透迷雾的对比
1.1.3 反向代理的不可替代性:
1.1.4 Nginx代理体系:proxy与upstream的默契配合
1.1.5 代理链路的全流程解析
1.1.6 避免常见误区:代理的进阶认知
1.2 ngx_http_proxy_module参数详解
参数1:proxy_set_header
参数2:proxy_buffering on;
参数3:proxy_buffer_size 4k;
参数4:proxy_buffers 8 4k;
参数5:proxy_busy_buffer_size 16k;
参数6: proxy_temp_path
参数7: proxy_max_temp_file_size
1.3 proxy_pass 代理七层转发
1.4 proxy_pass时url末尾带“/”与不带“/”的区别
二、代理层cache与处理cache
2.1 代理层的proxy_cache机制
2.2 代理层的proxy_cache应用
一、 ngx_http_proxy_module模块介绍
地址:Module ngx_http_proxy_module
1.1 代理的本质与应用场景:从中间层到工程实践
1.1.1 重新定义“代理”:不只是桥梁
在分布式架构中,代理(Proxy)常被比作“中间人”,但其核心价值远不止传递数据。它的核心使命是解耦上下游:
-
对下游:隐藏上游的拓扑细节(如服务器IP、负载策略),提供统一的接入入口。
-
对上游:过滤非法请求(如DDoS攻击)、预处理数据(如压缩/加密),减轻业务服务器压力。
PS: 上游与下游的解释
基本概念
下游(Downstream):请求的发起方或数据接收端,通常是更靠近客户端的一侧。
上游(Upstream):请求的接收方或数据提供端,通常是更靠近服务端的一侧。
核心规则:数据流动方向决定上下游关系,下游是“索取者”,上游是“提供者”。反向代理中的上下游,在典型Nginx反向代理场景中:
客户端(下游) → Nginx(上游) → 后端服务器(更上游)
客户端视角:Nginx是上游(直接交互的对象)。
Nginx视角:后端服务器是上游(实际处理请求的服务)。
后端服务器视角:Nginx是下游(请求来源)。
1.1.2 正向代理 vs 反向代理:穿透迷雾的对比
许多资料混淆二者的差异,关键在于视角和场景:
维度 | 正向代理 | 反向代理 |
---|---|---|
使用角色 | 客户端主动配置(如浏览器代理) | 服务端部署,客户端无感知 |
核心目的 | 突破访问限制(如科学上网) | 负载均衡、安全防护、缓存加速 |
典型场景 | 企业内网代理、爬虫IP池 | Nginx网关、API Gateway |
流量方向 | 集中客户端分散请求 → 统一出口 | 分散客户端请求 → 集中入口处理 |
1.1.3 反向代理的不可替代性:
安全性:屏蔽真实服务器IP,防止直接暴露于公网。
扩展性:通过横向扩展代理节点,轻松应对高并发。
灵活性:动态路由、A/B测试、灰度发布等高级功能的基础。
1.1.4 Nginx代理体系:proxy与upstream的默契配合
Nginx的代理能力由两大模块协同实现:
1) proxy模块:协议适配与流量治理
核心职责:
修改请求头(如
X-Forwarded-For
传递真实客户端IP)响应内容处理(如Gzip压缩、SSL证书卸载)
流量控制(限速、缓冲区优化、超时熔断)
关键配置示例:
location /api/ { proxy_pass http://backend; # 请求转发 proxy_set_header Host $host; # 透传原始域名 proxy_read_timeout 5s; # 防止慢请求阻塞 proxy_buffers 8 16k; # 内存缓冲区优化 proxy_http_version 1.1; # 强制使用HTTP长连接 }
2) upstream模块:连接池与负载均衡
核心职责:
维护后端服务器池(健康检查、故障转移)
负载均衡算法(轮询、权重、IP Hash等)
连接复用(Keepalive优化,降低TCP握手开销)
高阶用法:
upstream backend { zone backend_zone 64k; # 共享内存区,统计状态 server 10.0.1.1:8080 max_fails=3; # 自动剔除故障节点 server 10.0.1.2:8080 backup; # 备用服务器 least_conn; # 最少连接数策略 keepalive 32; # 连接池复用长链接 }
1.1.5 代理链路的全流程解析
当Nginx处理一个反向代理请求时,实际经历了多个阶段的精细化控制:
请求拦截:通过
location
匹配URL规则(如按路径或域名分流)。上游选择:从
upstream
池中按策略选取目标服务器。请求重写:修改请求头、URL路径(如
rewrite
规则)。数据中转:双向流式传输数据,支持零拷贝优化。
响应处理:过滤敏感头、压缩内容、记录日志。
异常熔断:超时、502错误自动重试或降级。
1.1.6 避免常见误区:代理的进阶认知
代理≠性能瓶颈:通过内核级优化(如Linux的sendfile机制),Nginx的代理吞吐可达百万QPS。
代理≠单向透传:可通过
sub_filter
修改响应内容,实现动态内容替换。代理≠仅限HTTP:Nginx的
stream
模块支持四层TCP/UDP代理(如数据库、游戏服务器)。
官网地址见:Module ngx_http_proxy_module
1.2 ngx_http_proxy_module参数详解
设置背景:
客户端:192.168.0.63 访问 www.51xue.com
代理层:192.168.0.64 配置 www.51xue.com转发到业务层的www.51xuebak.com
业务层:192.168.0.65 配置 www.51xuebak.com并处理请求到后端层
后端层:192.168.0.66 配置mysql集群
语法格式:
作用:proxy_set_header是用来设置请求头的,设置了请求头后,后端服务器就可以获取到这些变量值。
语法:proxy_set_header field value;
field :为要更改的项目,也可以理解为变量的名字,比如host
value :为变量的值
1.2.1 请求头重写:proxy_set_header的攻防博弈
proxy_set_header
是代理流量治理的「指纹修改器」,它决定了后端如何感知客户端身份。以下通过渗透测试场景解析其深层作用:
1.2.2 基础身份伪装:host与host与proxy_host
-
场景:隐藏真实业务域名,防止被扫描工具识别
location / { proxy_pass http://192.168.0.65; # 直连IP,不暴露域名 proxy_set_header Host www.mirr.com; # 伪装域名 }
-
攻击者视角:只能看到
www.mirr.com
,无法关联真实业务www.51xuebak.com
-
防御价值:增加黑产逆向工程成本,阻断基于域名的漏洞扫描
-
1.2.3 多租户隔离:$http_host的精准路由
当Nginx代理多个租户服务时:
server {
listen 80;
server_name tenant1.com;
proxy_set_header Host $http_host; # 透传原始域名
proxy_pass http://backend;
}
server {
listen 80;
server_name tenant2.com;
proxy_set_header Host $http_host;
proxy_pass http://backend;
}
-
后端服务:通过
Host
头区分租户,实现单集群多域名隔离,比如你请求的是tenant1.com,就自动匹配到第一个server下。 -
性能优势:避免为每个租户启动独立进程。
1.2.4 IP溯源体系:X-Forwarded-For的信任链构建
多层代理下的真实IP提取算法:
def get_real_ip(xff_header):
ips = xff_header.split(', ')
return ips[0] if len(ips) > 0 else None # 首段为真实客户端IP
-
安全威胁:若首段IP可伪造,可能导致IP欺骗
-
加固方案:
set_real_ip_from 192.168.0.0/16; # 只信任内网代理 real_ip_header X-Forwarded-For; real_ip_recursive on; # 跳过可信代理IP
1.2.5 缓冲机制:proxy_buffer的性能生死线
缓冲配置不当可直接导致吞吐量雪崩,通过Linux内核参数优化突破瓶颈:
1) 缓冲区的「三区两界」模型
-
Header区(proxy_buffer_size):存储响应头,建议4K对齐(匹配SSD块大小)
-
Body区(proxy_buffers):环形缓冲队列,推荐8×4K(适配HTTP平均包大小)
-
Busy区(proxy_busy_buffer_size):冲刷阈值,建议≥16K(减少内核态切换)
2) 零拷贝优化:sendfile与buffer的化学反应
启用内核级零拷贝:
proxy_buffering on;
sendfile on; # 绕过用户空间,直接从页缓存发送
-
性能收益:减少CPU拷贝次数,提升静态资源传输效率40%
-
适用场景:大文件下载、视频流传输
1.2.6 临时文件黑洞:proxy_temp的容灾设计
临时文件是代理服务的「阿喀琉斯之踵」,需从架构层面规避风险:
1)多级存储隔离
proxy_temp_path /dev/shm/nginx_temp 1 2; # 内存盘存储活跃文件
proxy_max_temp_file_size 2G; # 防磁盘写满
proxy_temp_file_write_size 16k; # 匹配SSD最佳写入单元
-
内存盘优势:I/O速度比SSD快100倍,系统重启自动清理
-
兜底方案:Crontab定时清理旧文件
*/30 * * * * find /dev/shm/nginx_temp -type f -mmin +30 -delete
2)故障注入测试
-
Case 1:磁盘空间耗尽 → 触发499日志并返回503状态码
-
Case 2:文件权限错误 → 通过SELinux沙盒自动修复
-
防御策略:
proxy_temp_path /var/nginx_temp 1 2; error_page 500 502 503 504 =503 @failsafe; location @failsafe { return 503 "Service Degraded"; }
1.3 proxy_pass 代理七层转发
公共代理参数文件:在跟nginx.conf配置文件同一目录下,建立一个proxy.conf文件,添加以下信息:
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_connect_timeout 90;
proxy_send_time 90;
proxy_read_time 90;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64;
-
然后再nginx.conf文件中包含proxy.conf文件进来:
server {
listen 80;
server_name ceshi1.10jqka.com.cn;
root /var/www/html;
proxy_ignore_client_abort on;
access_log logs/ceshi1.access.log main;
error_log logs/ceshi1.error.log notice;
location = /break {
rewrite ^/break/(.*) /test/$1 break;
proxy_set_header Via "";
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://192.168.205.187/last/$1;
}
location ^~ /last/ {
rewrite ^/last/(.*) /test/$1 last;
proxy_set_header Via "";
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://www.ceshi2.com/$1;
}
}
1.4 Nginx路径拼接规则:proxy_pass斜杠的「蝴蝶效应」
1.4.1 本质原理:URI的三种拼接模式
Nginx处理proxy_pass
路径时,根据目标URL是否含URI,分为三种处理策略:
proxy_pass格式 | 匹配模式 | 路径处理规则 |
---|---|---|
http://backend | 不含URI | 透传客户端完整路径 |
http://backend/ | 含URI(根路径) | 删除location匹配部分,拼接剩余路径 |
http://backend/api | 含URI(非根路径) | 同上,但保留自定义前缀 |
1.4.2 斜杠触发「路径手术」的底层逻辑
1) 规则公式
代理目标URI = proxy_pass的URI部分 + (客户端URI - location匹配部分)
2) 核心代码逻辑(伪代码)
def process_proxy_pass(client_uri, location_match, proxy_pass_uri):
if proxy_pass_uri.endswith('/'):
# 精确切除location匹配部分
cut_length = len(location_match)
return proxy_pass_uri + client_uri[cut_length:]
else:
# 全量透传(含location匹配部分)
return proxy_pass_uri + client_uri
1.4.3 四象限分析:斜杠的排列组合
假设客户端请求:http://nginx-host/test/tes.jsp
1) 象限1:location带斜杠 & proxy_pass带斜杠
location ^~ /test/ { # 匹配部分:/test/ proxy_pass http://backend/server/; # 目标URI:/server/ }
代理路径:
/server/ + (tes.jsp) = http://backend/server/tes.jsp
:'/server/ + (tes.jsp) = http://backend/server/tes
2) 象限2:location无斜杠 & proxy_pass带斜杠
location ^~ /test { # 匹配部分:/test proxy_pass http://backend/server/; # 目标URI:/server/ }
代理路径:
/server/ + (/tes.jsp) = http://backend/server//tes.jsp
(双斜杠可能导致后端路由异常)
3) 象限3:location带斜杠 & proxy_pass无斜杠
location ^~ /test/ { # 匹配部分:/test/ proxy_pass http://backend; # 目标URI:空 }
代理路径:
空 + (tes.jsp) = http://backend/test/tes.jsp
(透传原始路径,可能暴露内部结构)
4) 象限4:location无斜杠 & proxy_pass无斜杠
location ^~ /test { # 匹配部分:/test proxy_pass http://backend; # 目标URI:空 }
代理路径:
空 + (/test/tes.jsp) = http://backend/test/tes.jsp
1.4.4 高级调试:抓包验证路径传递
1) tcpdump抓包命令
tcpdump -i eth0 -A -s 0 'port 8080' | grep "GET /"
2) 测试案例对比
场景 | 预期路径 | 实际抓包结果 | |
---|---|---|---|
象限2配置 | /server//tes.jsp | GET //tes.jsp HTTP/1.1 | |
象限3配置 | /test/tes.jsp /测试/ | GET /test/tes.jsp HTTP/1.1 获取 /test/tes.jsp HTTP/1.1 |
总结:斜杠的「四要四不要」
要 | 不要 |
---|---|
保持location与proxy_pass斜杠一致 | 混合使用有无斜杠配置 |
用rewrite标准化路径 | 依赖后端容忍异常路径 |
开启merge_slashes优化 | 允许双斜杠出现在关键路径 |
定期扫描access_log验证 | 忽视HTTP 400类错误日志 |
通过理解路径拼接的量子化特性,可避免90%的代理配置故障。
二、代理层cache与处理cache
2.1 代理层的proxy_cache机制
-
proxy_cache和fastcgi_cache构成了Nginx的缓存。
-
proxy_cache主要用于反向代理时,对后端内容源服务器进行缓存,可能是任何内容,包括静态的和动态,缓存减少了nginx与后端通信的次数,节省了传输时间和后端宽带;
-
fastcgi_cache在下一章节说明。
-
涉及到的其他模块
-
清除缓存模块:ngx_cache_pure
-
扩展包下载地址:FRiCKLE Labs / nginx / ngx_cache_purge
-
2.2 代理层的proxy_cache的应用
http {
proxy_temp_path /memdisk/proxy/proxy_dir;
proxy_cache_path /memdisk/proxy levels=1:2 keys_zone=cache_proxy:1024m inactive=15m max_size=8168m;
server {
listen 80;
server_name ceshi1.10jqka.com.cn;
root /var/www/html;
proxy_ignore_client_abort on;
access_log logs/ceshi1.access.log main;
error_log logs/ceshi1.error.log notice;
location @fetch {
internal;
#proxy_next_upstream error timeout invalid_header http_502;
#proxy_next_upstream_timeout 10;
proxy_cache cache_proxy;
proxy_cache_key $uri$is_args$args;
proxy_cache_valid 200 2m;
proxy_cache_use_stale error timeout invalid_header http_500 http_502;
proxy_set_header Host stock.10jqka.com.cn;
proxy_pass http://front_server;
}
}
}
2.2.1 缓存清理
-
浏览图片文件:http://www.osyunwei.com/images/nopic.gif
-
清除这个文件缓存:http://www.osyunwei.com/purge/images/nopic.gif
-
提示:Successful purge,缓存文件清除成功,如果这个文件没有被缓存过,则提示:404 Not Found
备注:
-
purge是ngx_cache_pure 模块指令
-
images/nopic.gif 是要清除的缓存文件URL路径
2.2.2 proxy_cache_path的引用介绍
1) proxy_temp_path
:缓存构建的「临时车间」proxy_temp_path /memdisk/proxy/proxy_dir;
1.1) 核心作用
当Nginx从上游服务器接收响应时,若响应数据无法直接写入缓存(如大文件分块传输或缓存未就绪),会先存入临时目录,待完整接收后再原子化移动到缓存区。
1.2) 参数详解
路径设计:
/memdisk/proxy/proxy_dir
/memdisk
:通常为内存盘挂载点(如tmpfs),利用内存I/O加速临时文件操作三级目录结构:避免单目录文件数过多引发inode竞争
未显式配置的参数:
levels
:默认无层级,适用于高频写入场景
use_temp_path
:默认开启,需结合proxy_cache_path
的use_temp_path
调优1.3) 性能影响
存储介质 写入延迟 适用场景 内存盘 0.1-1ms 0.1-1 毫秒 高频交易行情数据 SSD 固态硬盘 2-5ms 电商大促页面缓存 机械硬盘 10-50ms 低频访问日志归档 1.4 风险预警
内存盘容量溢出:若临时文件总量超过内存盘大小,引发OOM(需监控
/memdisk
使用率)原子移动失败:网络抖动导致临时文件未完整迁移,触发
proxy_next_upstream
重试机制
2)
proxy_cache_path
:缓存系统的「核心仓库」proxy_cache_path /memdisk/proxy levels=1:2 keys_zone=cache_proxy:1024m inactive=15m max_size=8168m;
参数逐层拆解
2.1) 存储拓扑:
levels=1:2
目录结构:
/memdisk/proxy ├── a/ ← 1级目录(1字符命名,16进制) │ ├── 1b/ ← 2级目录(2字符命名) │ │ └── 缓存文件(MD5哈希值命名) │ └── c3/ └── d/
设计价值:
避免单目录文件数超过ext4的5000万inode限制
提升SSD随机读性能(目录分散降低锁竞争)
2.2) 内存元数据区:
keys_zone=cache_proxy:1024m
内存消耗公式:
每条缓存元数据 ≈ 200字节 最大缓存键数量 = 1024MB / 200B ≈ 5,368,709 条
2.3) 生命周期管理
inactive=15m
:
含义:15分钟内未被访问的缓存视为冷数据,触发LRU淘汰
误删防御:结合
proxy_cache_background_update on
实现热更新保活
max_size=8168m
:
动态权重:实际占用空间 = 文件大小 + 文件系统块开销(通常+10%)
溢出处理:触发
cache manager
进程按LRU清理,写入error_log
警告2.4) 极端场景测试
测试用例 预期行为 日志关键字 写入8.5G缓存 触发LRU清理 cache manager process exited abnormally
内存盘写满 返回502错误 no space left on device
共享内存溢出 新缓存被拒 could not add new key