又是一个凌晨三点,电话炸响——线上正向代理集群全挂了。一堆后端服务拿不到真实客户端IP,反爬策略全废,TLS证书链验证超时……说实话,当时我心里就一个念头:为什么之前没有人把这些坑给我讲清楚?
正向代理,不就是中间人代理吗?客户端把请求发给代理,代理再去访问目标服务器,原路返回数据。但魔鬼藏在实现细节里。从socket缓冲区设计,到内核协议栈旁路,再到连接复用的队头阻塞,每个环节都能让你脱层皮。本文不是科普,是一场基于血泪教训的技术复盘,顺带给你几组要命的压测数字,和可落地的解法。
协议栈下的数据绞杀:零拷贝的救命稻草
传统代理软件(比如老版本Squid)怎么转发数据?用户态读一遍,再写回内核发送出去。两次上下文切换,四次数据拷贝。对于大并发小包,CPU 瞬间被吃光。这就是为什么你看到代理的CPU si(软中断)占比高得离谱。
后来人们学聪明了。Linux 2.6 引入了 splice 系统调用,数据直接在两个 socket 之间管道传输,不走用户态。Envoy 的转发路径就是围绕这个设计的——它们称之为 zero-copy forwarding path。你想想,数据就像在两个socket之间直接打通一条隧道,内核里完成清零与移动。这个优化在长连接下尤其明显,延迟直接砍半。下面这张图可以帮你理解数据流:

但别高兴太早。splice 要求两个 fd 都是管道或 socket,并且有各种限制。比如,如果代理需要对数据做内容替换(修改Header),那就不能完全零拷贝,必须把数据读到用户态处理。Envoy 为此做了分层抽象:ReadFilter 和 WriteFilter 管道,允许在不同阶段介入。说实话这个设计太漂亮了——你可以在不改业务逻辑的前提下,把一个复杂的代理拆解成一系列可插拔的过滤器,就像搭乐高。性能?压测表明,在启用全套 Filters(如TLS解密、Header重写、限流)的情况下,Envoy 仍能维持 80% 的裸转发性能,这得益于它那个极其精巧的线程模型和内存池管理。而同样的场景放在 Squid 上,性能直接腰斩——因为它的多进程模型在高并发下上下文切换开销巨大。
数字说话:压测场上谁在裸泳
没有对比就没有伤害。我们团队专门搭建了一个压测环境:硬件统一为 Intel Xeon Gold 64C @ 2.2GHz,128G RAM,10GbE 网卡。客户端用 wrk2 模拟 10K 长连接,请求一个100字节的静态文件。后端是一组简单的 Nginx 服务器,返回固定内容。代理软件配置都力求最优。结果如下:
Envoy:87万req/s,P50延迟0.3ms,P99延迟1.2ms,CPU idle 还剩 35%。
Nginx:69万req/s,P50 0.5ms,P99 2.5ms,CPU idle 25%。
Squid(多进程,8 workers):33万req/s,P50 2ms,P99 8.3ms,CPU idle 12%但si高。
看到没?Squid被按在地上摩擦,而Nginx虽然是高性能Web服务器,但作为正向代理其架构没有专门为连接复用和零拷贝做极致优化,所以依然跑不过Envoy。更可怕的是,在突发放大(比如缓存失效导致大量回源)时,Squid 的 P99 会直接飙到 200ms,引发调用链超时雪崩——这就是血的教训。

不过话说回来,Envoy 的高性能不是白给的,配置复杂度也高了一大截。如果你团队没有足够强的内核和网络功底,贸然上 Envoy 反而会翻车。所以我通常建议:10万QPS以下,Nginx 足够了;再往上,或者需要丰富的七层路由功能,Envoy 是首选。至于 Squid … 让它留在历史教科书里吧,除非你维护的古老系统非它不可。
三大落地深坑:每一口都带血
下面这三点,可以说是我用无数个通宵换来的教训。你最好拿小本本记下来。
坑1:源IP的幽灵——丢了,就再也找不回
正向代理天然就是匿名代理。后端服务器看到的永远是代理IP。这导致安全审计、频控、用户画像彻底失效。一开始我们尝试在HTTP头里加 X-Forwarded-For,但业务方众多,有的服务压根不读这个头;而且如果你代理的是HTTPS,不解密的话,你连HTTP头都加不了。后来我们强制开启了 Proxy Protocol V2,在TCP连接建立时就把原始客户端信息透传过去。但这又要求后端的 Web Server(或负载均衡)支持 Proxy Protocol。我们遇到一个遗留的 NodeJS 服务,用的库根本不支持,只好在代理和后端之间插入一个 Envoy sidecar 做端口转换和协议解析。坑!但这总算是通了。
坑2:HTTPS中间人——证书管理噩梦
为了安全,公司要求对出口流量做内容审计。也就是说正向代理必须解密HTTPS,检查后再重新加密发出去。这就需要代理拥有一个CA证书,并动态签发服务器证书,客户端也必须信任这个CA。起初我们手动管理证书,结果每三个月一次人肉续签,几次差点出了大事故。后来我们引入 cert-manager + step-ca,实现了短期证书自动签发和轮转,并且对接了 CT log 监控。但性能代价也很大:每新建一个TLS连接,都要生成一个证书,私钥签名操作会占用CPU。我们后来将证书缓存到 LRU 内存中,同一个目标域名复用同一个生成的证书,性能才回升。这里提醒一句:千万别在代理上做耗时的 OCSP 在线验证,血的教训——我们在压测时打开了 OCSP Must-Staple,结果 OCSP 响应超时导致握手大量失败,QPS 直接掉到 1/10。生产上,预装 OCSP 响应或关闭强制。
坑3:连接复用的队头阻塞——H2的多路复用陷阱
HTTP/2 的多路复用看起来很美好:一个TCP连接上并发多个流,消除TCP握手开销。但在正向代理场景下,后端可能有多个不同服务,响应时间千差万别。如果一个慢请求占用了后端的连接资源,同连接上其他快请求就会被阻塞——这正是 H2 的队头阻塞。我们曾经在线视频业务因为一个图片优化服务的后端慢查询,拖死了整个代理集群。解决方案?精细化的连接池策略:为每个后端上游维护独立的连接池,并设定最大请求数和超时。另外,我们逐步切到 HTTP/3,用 QUIC 解决传输层的队头阻塞,效果立竿见影。但 HTTP/3 在代理场景的成熟度还需观望,目前我们只是在核心链路用Envoy开启了 H3 upstream,反馈良好。
最后,没有总结,没有鸡汤。下次当你再配置正向代理时,记得我今晚失去的睡眠。技术就是这样,表面平静,底下暗流汹涌。