上游代理(upstream proxy)是指另一台代理服务器将客户端请求转发到的那台代理服务器,由此形成一条链路,其中第一台代理相当于上游代理的客户端。
什么是上游代理?
当客户端向代理服务器发送请求时,该代理服务器通常直接从互联网上的源站获取所请求的资源。但在许多架构设计中,第一台代理服务器并不直接与源站通信,而是把请求转发给另一台代理服务器,即上游代理。这第二台代理再处理该请求:或从自身缓存中返回,或继续转发给再上一级的上游代理,或从源站获取。
使用上游代理的主要目的包括:
- 分层安全: 增加一层额外的安全防护与访问控制。
- 专用功能: 将内容过滤、病毒扫描或高级缓存等特定任务交由专门的代理处理。
- 网络分段: 打通不同的网络分段,或提供对外部网络的受控访问。
- 匿名与隐私: 通过多跳转发隐藏原始客户端的 IP 地址。
- 绕过限制: 让流量经由不同的地理位置或网络转发,以绕过地域封锁或网络限制。
- 负载均衡: 在多个上游代理或源站之间分配请求。
带上游代理的请求流程为:Client -> Proxy A -> Upstream Proxy B -> Origin Server。在该场景中,Proxy A 被配置为使用 Proxy B 作为其上游。
级联代理详解
级联代理(cascading proxies),也称代理链(proxy chaining),指的是把多台代理服务器按顺序排列的架构:每台代理把请求转发给链上的下一台,直到最后一台代理与源站通信。这为客户端请求构建了一条多跳路径。
级联代理的优点
- 更强的安全性: 每台代理都可以执行自己的一套安全策略、身份验证和访问控制。
- 模块化架构: 不同代理可以专注于不同功能(例如一台做缓存,一台做 WAF,一台做出口管控)。
- 复杂路由: 支持精细的路由逻辑,把特定类型的流量导向不同的链路或地理位置。
- 匿名性更高: 跳数越多,追溯原始客户端就越困难。
- 冗余绕行: 当链中某台代理故障或被封时,可提供备用路径。
级联代理的缺点
- 延迟增加: 每增加一跳代理,都会带来处理延迟和网络延迟。
- 复杂度高: 配置与运维更加繁琐,尤其在调试和排障时。
- 单点故障: 若未做冗余设计,链中任意一台代理故障都可能中断服务。
- 调试困难: 跨多台服务器追踪请求路径并定位问题并不容易。
- 首部管理: 正确处理
X-Forwarded-For和Via等首部,对日志记录和源站识别至关重要。
配置上游代理
以下是常见代理服务器的配置示例,演示如何让一台代理把请求转发到上游。
Nginx(作为面向上游的 reverse proxy)
Nginx 通常作为 reverse proxy 运行。要让一个 Nginx 实例把请求转发到上游代理,需在 location 块内的 proxy_pass 指令中指定上游代理的地址。
# Nginx 配置(例如 proxy.example.com)
# 该 Nginx 实例作为 Proxy A,转发到 Upstream Proxy B。
http {
upstream upstream_proxy_b {
# 上游代理(Proxy B)的地址
server 192.168.1.100:3128; # 示例:Proxy B 的 IP 与端口
# 可选:若 Proxy B 有多个实例,可添加多台服务器以实现负载均衡/故障转移
# server 192.168.1.101:3128;
}
server {
listen 80;
server_name proxy.example.com;
location / {
# 将请求转交给已定义的 upstream_proxy_b
proxy_pass http://upstream_proxy_b;
# 代理链推荐设置的首部
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_set_header Via "1.1 $hostname"; # 将当前代理加入 Via 首部
}
}
}
Squid(作为面向上游的 forward proxy)
Squid 常被用作 forward proxy。要让 Squid 使用上游代理,需使用 cache_peer 指令。
# Squid 配置(例如 proxy-a.conf)
# 该 Squid 实例作为 Proxy A,转发到 Upstream Proxy B。
# 定义上游代理(Proxy B)
# 语法:cache_peer hostname type http_port icp_port [options]
# type:parent 或 sibling(上游一般用 parent)
# http_port:上游代理的 HTTP 端口
# icp_port:ICP 端口(不使用或未知时通常为 0)
cache_peer 192.168.1.100 parent 3128 0 no-query default_parent
# 多个上游代理的示例(Proxy B 和 Proxy C)
# cache_peer 192.168.1.101 parent 3128 0 no-query round-robin
# cache_peer 192.168.1.102 parent 3128 0 no-query
# 允许客户端使用本代理的访问控制
acl localnet src 192.168.0.0/24 # 示例:您的客户端网络
http_access allow localnet
http_access deny all
# 指定 Squid 监听客户端请求的端口
http_port 3129 # Proxy A 监听 3129
# 可选:添加 Via 首部
via off # Squid 默认会添加 Via 首部;'off' 表示 Squid 不添加自己的 'Via' 首部,
# 但仍会转发已有的首部。若要确保它*添加*自己的首部,请删除此行或设为 'on'。
# 对于级联场景,通常更推荐 'via on' 或保持默认行为。
在这份 Squid 配置中,proxy-a 监听 3129 端口,并由于 default_parent 选项,把所有请求转发到 192.168.1.100:3128(Proxy B)。
配置级联代理
要搭建级联代理,需要把链中每台代理都配置为指向下一台。
场景:Client -> Proxy A -> Proxy B -> Internet
该场景包含链上的两台代理。
Proxy A 配置(前端代理)
Proxy A 是客户端的第一接入点,负责把请求转发给 Proxy B。
Nginx 作为 Proxy A:
(参见上文“配置上游代理”中的 Nginx 示例。upstream_proxy_b 块和 proxy_pass http://upstream_proxy_b; 指令使 Nginx 把请求发送到 Proxy B。)
Squid 作为 Proxy A:
(参见上文“配置上游代理”中的 Squid 示例。cache_peer 192.168.1.100 parent 3128 0 no-query default_parent 指令使 Squid 把请求发送到 Proxy B。)
Proxy B 配置(中间/上游代理)
Proxy B 接收来自 Proxy A 的请求,然后转发到互联网(若链路更长,则转发给另一台上游代理)。
Nginx 作为 Proxy B:
如果 Proxy B 同样是 Nginx 实例,通常会把它配置为向源站转发的标准 reverse proxy。如果它仅作为出口点或再下一级代理,其配置与 Proxy A 类似,只是 proxy_pass 的目标是下一台代理或真正的源站。
# Proxy B 的 Nginx 配置(192.168.1.100)
# 该 Nginx 实例接收来自 Proxy A 的请求并转发到互联网。
http {
server {
listen 3128; # Proxy B 监听 3128,接收来自 Proxy A 的请求
server_name proxy-b.example.com; # 或直接监听 IP
location / {
# 此处 Proxy B 可以依据客户端的原始请求直接转发到源站
# 或者,若链路继续,则转发给*另一台*上游。
# 为简化起见,这里假设它依据原始请求转发到互联网。
# Nginx 作为 forward proxy 更为复杂,通常涉及动态解析。
# 更常见的场景是 Nginx 作为已知源站集合的 reverse proxy,
# 或作为单一“下一跳”代理的前置。
# 示例:若 Proxy B 转发到某个已知的特定源站
# proxy_pass http://www.example.com;
# 若 Proxy B 需要作为面向任意域名的通用 forward proxy(在 Nginx 中较少见)
# 这需要动态解析,通常由 Squid 之类的专用 forward proxy 处理。
# 要让 Nginx 充当通用 forward proxy,往往需要自定义 Lua 脚本或模块。
# 更简单的 Nginx Proxy B 可能只是转交给*另一个*固定的上游。
# 若要真正面向“互联网”转发,通常首选 Squid。
# 若 Proxy B 只是出口点且不知道最终源站(如 Squid 那样)
# 这正是 Nginx 作为*通用*forward proxy 能力受限之处。
# 如果 Proxy B 是 Nginx,它更可能是针对特定服务的 reverse proxy,
# 或者是既定链路中的下一跳,且该下一跳同样是固定的。
# 就级联而言,如果 Nginx 是 Proxy B,通常会把它配置为
# 转交给*已知*的下一跳或已知的源站。
# 对于面向“通用互联网”的出口,Squid 更合适。
# 这里假设 Proxy B 转发到一个已知的*最终*上游,例如匿名化代理
# 或某个特定的出口网关。
proxy_pass http://final_egress_proxy:8080; # 示例:转发到最终出口代理
# 或者,若它是回源前的最后一跳且 Nginx 配置了动态上游解析:
# proxy_pass $scheme://$host$request_uri; # 需要为动态 forward proxy 做自定义配置
# 为简化起见,这里假设 Proxy B 是提供通用互联网访问的 Squid 代理。
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_set_header Via "1.1 $hostname";
}
}
}
Squid 作为 Proxy B:
Proxy B 接收来自 Proxy A 的请求,可配置为直接从互联网获取,或转发给再上一级的上游。如果它是通往互联网前的最后一台代理,就不需要指向其他代理的 cache_peer 指令(除非用于特定域名或故障转移)。
# Proxy B 的 Squid 配置(192.168.1.100)
# 该 Squid 实例接收来自 Proxy A 的请求并从互联网获取资源。
# Proxy B 监听 3128,接收来自 Proxy A 的请求
http_port 3128
# 可选:若 Proxy B 自身也有上游(例如 ISP 代理或其他特定代理)
# cache_peer upstream.isp.com parent 8080 0 no-query default_parent
# 允许 Proxy A(及其他授权客户端)连接的访问控制
acl proxy_a_network src 192.168.1.0/24 # 示例:Proxy A 所在的网络
http_access allow proxy_a_network
http_access deny all
# 按 Proxy B 的需要配置缓存、日志等
进阶级联:条件上游
若需要更复杂的路由,您可以把特定域名或路径的请求发送到不同的上游代理。
Nginx 条件上游:
http {
# 通用流量的 upstream 组
upstream general_upstream {
server 192.168.1.100:3128; # Proxy B
}
# 特定敏感流量的 upstream 组
upstream secure_upstream {
server 192.168.2.200:4444; # 安全代理 Proxy C
}
server {
listen 80;
server_name proxy.example.com;
location /secure/ {
# 发往 /secure/ 的请求交给安全代理 Proxy C
proxy_pass http://secure_upstream;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location / {
# 其余所有请求发往 Proxy B
proxy_pass http://general_upstream;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
Squid 条件上游:
# 定义多个 cache_peer
cache_peer 192.168.1.100 parent 3128 0 no-query name=general_proxy
cache_peer 192.168.2.200 parent 4444 0 no-query name=secure_proxy
# 用于识别特定流量的 ACL
acl secure_domains dstdomain .secure.example.com
acl secure_urls url_regex ^https://secure\.
# 依据 ACL 分流请求
cache_peer_access secure_proxy allow secure_domains
cache_peer_access secure_proxy allow secure_urls
cache_peer_access general_proxy allow all
# 确保未匹配任何显式 peer 访问规则的流量走直连或 default_parent
# 若未设置 default_parent,请求可能失败,或依据 'never_direct'/'always_direct' 走直连
# 通常更好的做法是设置 default_parent,或在最后加一条兜底的 'cache_peer_access'。
级联代理的关键考量
延迟与性能
链中每台代理都会增加处理时间和网络延迟。尽量减少跳数,并确保代理与网络链路具备高性能,这一点至关重要。请监控端到端延迟以发现瓶颈。
安全与身份验证
- 代理间身份验证: 在各代理之间实施身份验证(例如 IP 白名单、共享密钥、客户端证书),防止中间代理被未授权访问。
- SSL/TLS 终止: 确定在何处进行 SSL/TLS 终止。若代理会解密流量,须做好证书管理,并为后续跳数重新加密。
- 访问控制: 在每台代理上配置严格的访问控制列表(ACL),只允许来自授权上游/下游代理或客户端的流量。
调试与排障
跨多台代理追踪请求可能十分复杂。
* X-Forwarded-For 首部: 该首部记录原始客户端 IP 地址以及链中后续代理的 IP。请确保它在每一跳都被正确追加(Nginx 中用 $proxy_add_x_forwarded_for)。
* Via 首部: Via 首部标明请求经过的中间代理。每台代理都应把自身标识加入该首部。
* 日志记录: 集中式日志与关联 ID 对于在整条代理链上追踪请求必不可少。
负载均衡与故障转移
为保证高可用并分散流量,请为上游代理配置负载均衡。
* Nginx: 在 upstream 块中使用多条 server 指令,并配合 least_conn、round_robin、backup、down 等选项。
nginx
upstream proxy_b_cluster {
server 192.168.1.100:3128 weight=5; # 主 Proxy B
server 192.168.1.101:3128 backup; # 备用 Proxy B
server 192.168.1.102:3128; # 另一台主 Proxy B
least_conn; # 使用最少连接算法
}
# 然后 proxy_pass http://proxy_b_cluster;
* Squid: 使用多条 cache_peer 指令,并配合 round-robin、weighted-round-robin、failover、no-query 等选项。
squid
cache_peer 192.168.1.100 parent 3128 0 no-query weight=10
cache_peer 192.168.1.101 parent 3128 0 no-query weight=5
cache_peer 192.168.1.102 parent 3128 0 no-query round-robin
首部管理
谨慎管理 HTTP 首部至关重要。除 X-Forwarded-For 和 Via 之外,还应考虑用 Proxy-Authorization 向上游代理进行身份验证,并按策略确保其他相关首部被正确转发或剥离。
对比:单一上游与级联上游
| 特性 / 方面 | 单一上游代理 | 级联上游代理 |
|---|---|---|
| 复杂度 | 低;配置与运维更简单。 | 高;跨多台服务器的配置、运维与调试都很繁琐。 |
| 延迟 | 开销极小;仅多一跳网络。 | 开销更大;每台代理都带来一跳网络和处理延迟。 |
| 灵活性 | 受限于单台代理的能力。 | 高;可在每一环节实现专用功能并做复杂路由。 |
| 安全层级 | 单层安全与访问控制。 | 多层安全;每台代理可执行不同策略。 |
| 匿名性 | 相对源站提供基本匿名性。 | 匿名性更强;多跳转发使追溯原始客户端更困难。 |
| 调试 | 相对直接。 | 有难度;需要谨慎管理首部(X-Forwarded-For、Via)和日志。 |
| 故障影响 | 唯一上游故障会中断全部流量。 | 若未配置故障转移,链中任意代理故障都可能中断服务。 |
| 使用场景 | 基础内容过滤、缓存、简单访问控制。 | 高级安全、合规、地域路由、专用服务、高匿名需求。 |
