HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 代理技术全栈手册

    • 代理技术全栈手册 - HiHuo
    • 原理篇

      • 第01章 代理是什么:正向 / 反向 / 透明 / 隧道的统一模型
      • 第02章 代理与网络层级:L3 / L4 / L5 / L7 在哪里截断流量
      • 第03章 一个请求穿过代理的一生:连接生命周期全景
    • 协议篇

      • 第04章 HTTP 代理协议:绝对 URI、CONNECT 隧道、转发头与连接池
      • 第05章 HTTPS 与 TLS 代理:终止 / 透传 / MITM / SNI / mTLS
      • 第06章 SOCKS 协议:SOCKS4/4a/5 与 UDP ASSOCIATE 报文级解析
      • 第07章 HTTP/2、gRPC 与 HTTP/3(QUIC) 代理的挑战
      • 第08章 代理自动配置:PAC / WPAD / 系统代理 / NO_PROXY
    • 层级与转发篇

      • 第09章 L4 代理:TCP/UDP 转发与连接级负载均衡
      • 第10章 L7 代理:协议感知与基于内容的路由
      • 第11章 透明代理:iptables REDIRECT/DNAT、TPROXY 与 eBPF 劫持
      • 第12章 数据搬运的艺术:splice / sendfile / 零拷贝 / io_uring
    • 组件横评篇

      • 第13章 Nginx / OpenResty:反向代理、upstream 与 Lua 可编程
      • 第14章 HAProxy:L4/L7、ACL、健康检查与 stick table
      • 第15章 Envoy:xDS 动态配置与 filter chain,为何是云原生数据面
      • 第16章 Traefik / Caddy:自动服务发现与自动 HTTPS
      • 第17章 Squid 与正向/缓存代理:企业出网、缓存与审计
      • 第18章 mitmproxy:抓包、改包、脚本化调试
      • 第19章 内网穿透与隧道:frp / gost / SSH 隧道 / ngrok
      • 第20章 科学上网生态的技术原理(技术中立)
    • 多语言手写篇

      • 第21章 Go:100 行手写 HTTP/CONNECT + SOCKS5 代理
      • 第22章 Rust:基于 tokio 的高性能 TCP 代理
      • 第23章 Python:asyncio 实现,适合调试与脚本
      • 第24章 C:epoll 裸写与零拷贝,及语言选型对比
    • 容器与K8s篇

      • 第25章 Docker 里的代理:HTTP_PROXY、build/pull 与 daemon 配置
      • 第26章 Sidecar 与流量劫持:Istio init-container 的 iptables 原理
      • 第27章 Ingress 与南北流量:Ingress-nginx 与 Gateway API
      • 第28章 Egress 与出网治理:出口网关、registry mirror、审计
      • 第29章 Service Mesh 数据面:Envoy Sidecar 全链路
    • 进阶篇

      • 第30章 可编程代理:Lua / Wasm / eBPF / xDS,代理的"软件定义"
      • 第31章 性能调优:并发模型、连接池、超时与重试、压测
      • 第32章 排错决策树:502 / 504 / 握手失败 / 环路 / 泄漏
      • 第33章 代理安全:开放代理、SSRF、凭证泄漏与攻击面
    • 底层机制篇

      • 第34章 代理的背压与流控:一个代理最难的部分
      • 第35章 socket 与 TCP 状态机:半关闭、超时、连接生命周期
      • 第36章 HTTP/2 帧、流控与 HPACK:h2 代理的内部机制
      • 第37章 负载均衡算法推导与韧性状态机
      • 第38章 Capstone:把玩具代理改造成生产级骨架
    • 综合实战篇

      • 第39章 企业多跳转发链:拓扑、协议矩阵与贯穿性难题
      • 第40章 端到端实战:把 6 类流量全代理通
      • 第41章 更刁钻的流量:gRPC、长轮询、WebRTC、大文件、双向流
      • 第42章 可落地完整参考实现:一套能跑的多协议转发栈
    • 附录

      • 附录 A:代理协议报文速查(HTTP / SOCKS / PAC / PROXY protocol)
      • 附录 B:组件选型决策树
      • 附录 C:抓包与命令速查

第09章 L4 代理:TCP/UDP 转发与连接级负载均衡

学习目标

  • 理解 L4 代理"连接级"负载均衡与 L7"请求级"的本质差异及长连接陷阱
  • 掌握 L4 负载均衡的两种数据路径:代理模式 vs LVS 的 NAT/DR/TUN
  • 搞懂 UDP "无连接"如何被代理成"伪会话"
  • 用 PROXY protocol 解决"L4 后端拿不到真实客户端 IP"的经典难题

前置知识

  • 第02章 网络层级(L4 见端口+字节流)
  • 第10章 L7 代理(对照阅读)、网络手册 · 负载均衡

原理

L4 代理在做什么

L4 代理在两条传输层连接(TCP 或 UDP)之间搬字节,不解析应用协议(第02章)。它的决策只基于"目标 IP/端口"和"连接"这个粒度。一条 TCP 代理本质就是 第21章 里那段 io.Copy 双向拷贝,再配上后端选择。

连接级 vs 请求级:最关键的一条分水岭

L4(连接级):一条客户端连接 ── 整体绑定 ──▶ 一个后端
              这条连接上的所有请求,全去同一个后端

L7(请求级):一条客户端连接上的每个请求 ── 独立选择 ──▶ 可去不同后端

这条差异有个致命的长连接陷阱:

L4 LB 在连接建立时选一次后端,之后整条连接焊死。如果客户端用长连接(HTTP keep-alive、gRPC、数据库连接池),那这条连接的所有流量永远去同一后端。扩容时新加的后端会长期没流量——因为老长连接还在,新连接才会被分到新后端。

这就是为什么:用 L4 LB 代理 gRPC(一条 h2 长连接跑无数请求,第07章)会导致负载严重不均——必须用 L7 LB(按请求分发) 或客户端侧负载均衡。"gRPC 负载不均"是 L4/L7 选错的头号案例。

负载均衡算法

L4 LB 在连接建立时按算法选后端:

算法行为适用
轮询 round-robin依次分配后端同质、连接均匀
最少连接 least-conn选当前连接数最少的连接时长差异大
源地址哈希 source-hash同源 IP 固定到同后端会话保持(无 cookie 时)
一致性哈希后端增减时扰动最小缓存亲和、Maglev
随机 random / P2C随机(或两选一取优)大规模、低开销

两种数据路径:代理模式 vs LVS 三模式

L4 负载均衡有两大流派,数据路径完全不同:

流派一:代理模式(双连接,全流量过 LB)

代理与客户端、代理与后端各建一条连接,进出流量都经过 LB。HAProxy mode tcp、Nginx stream 属此类。简单、可控,但 LB 是双向流量的瓶颈。

Client ◀──TCP──▶ [L4 代理] ◀──TCP──▶ 后端    (回程也过代理)

流派二:LVS/IPVS(内核态转发,三种模式)

LVS 工作在内核 netfilter,性能极高。它更接近"转发"而非"代理"(不建两条独立 TCP,第02章 的 L3/L4 边界),但 L4 LB 必讲:

模式机制回程流量特点
NAT改写目标 IP,回程也经 LB 改回过 LB简单,LB 是回程瓶颈
DR(直接路由)只改目标 MAC,IP 不变不过 LB,后端直接回客户端高性能,要求同二层
TUN(隧道)IPIP 隧道封装转发不过 LB可跨网段

DR 模式的精髓:请求过 LB,响应直接从后端回客户端(DSR,Direct Server Return),绕开 LB。因为多数场景响应远大于请求(下载),DSR 让 LB 只扛请求流量,吞吐倍增。代价是组网约束(后端要配 VIP、同二层)。

现代云原生里,Cilium/Maglev 用一致性哈希做无状态 L4 LB,结合 XDP/eBPF(第11章、第12章)达到线速。

UDP 代理:给"无连接"造一个"伪会话"

UDP 没有连接概念,但代理要把"同一个客户端的来回包"对应起来,于是按四元组(源IP:源端口 → 目的IP:目的端口)建立临时会话映射:

收到客户端 UDP 包(src=C:5000) → 建映射,转发给后端B → 记住 C:5000↔B
后端 B 回 UDP 包 → 查映射,转回 C:5000
一段时间无包 → 超时回收映射(UDP 没有 FIN,只能靠超时)
  • 难点:超时设置(太短断会话,太长占内存)、无握手所以无法"拒绝"、QUIC/HTTP3 还要考虑连接迁移(第07章)
  • 用在:DNS、QUIC、syslog、游戏、VoIP

健康检查:L4 只能查"端口通不通"

L4 LB 的健康检查只能做到传输层:

  • 被动:转发失败(连接被拒/超时)就摘除
  • 主动:周期性 TCP connect 探测端口,能连上就算健康

局限:端口开着 ≠ 应用正常。一个后端 TCP 端口通、但应用已死锁返回 500,L4 健康检查发现不了——这要 L7 的应用层健康检查(GET /healthz,第10章)。

PROXY protocol:找回丢失的客户端 IP

L4 代理(代理模式)转发后,后端看到的源 IP 是代理的 IP,真实客户端 IP 丢了。L7 可以加 X-Forwarded-For(第04章),但 L4 不解析应用层、加不了 HTTP 头。HAProxy 发明的 PROXY protocol 解决此问题:在 TCP 数据流的最开头,先发一行明文(或二进制)声明真实四元组。

PROXY TCP4 198.51.100.7 203.0.113.1 56324 443\r\n   ← v1:代理先发这一行
<之后是真实的应用数据……>                              ← 后端先解析这行,得到真实客户端 IP
  • v1:人类可读文本;v2:二进制,更高效,支持更多元数据
  • 前提:后端必须懂 PROXY protocol 并配置接收,否则会把这行当成应用数据 → 协议错乱。是"配了 PROXY protocol 后端却 400"的根因。

️ 实现 / 命令

实验一:HAProxy 做 L4(TCP)负载均衡

# haproxy.cfg —— mode tcp 即 L4
defaults
    mode tcp
    timeout connect 5s
    timeout client  30s
    timeout server  30s

frontend ft_mysql
    bind *:3306
    default_backend bk_mysql

backend bk_mysql
    balance leastconn                 # 最少连接
    server db1 10.0.0.11:3306 check   # check = TCP 健康检查
    server db2 10.0.0.12:3306 check
    server db3 10.0.0.13:3306 check send-proxy   # send-proxy = 向后端发 PROXY protocol

mode tcp 让 HAProxy 代理任意 TCP(这里是 MySQL,L7 的 HTTP 代理可代理不了它)。

实验二:Nginx stream 做 L4 + 源哈希会话保持

stream {
    upstream backend {
        hash $remote_addr consistent;   # 源地址一致性哈希 → 会话保持
        server 10.0.0.11:443;
        server 10.0.0.12:443;
    }
    server {
        listen 443;
        proxy_pass backend;
        proxy_timeout 30s;
    }
}

实验三:socat 做 UDP 转发

# 把本地 5353/udp 转发到上游 DNS 8.8.8.8:53,体会 UDP 伪会话
socat -T15 UDP-LISTEN:5353,fork UDP:8.8.8.8:53 &
dig @127.0.0.1 -p 5353 example.com +short
# -T15:15 秒无包就回收会话映射(UDP 没有 FIN,必须靠超时)

实验四:验证 PROXY protocol

# 后端用 socat 打印收到的原始字节,能看到开头那行 PROXY ...
socat -v TCP-LISTEN:8000,reuseaddr,fork STDOUT &
# 让 HAProxy(send-proxy)转发一条连接过来,后端首行会是:
# PROXY TCP4 198.51.100.7 10.0.0.1 56324 8000

排错

现象根因解决
gRPC/长连接后端负载严重不均L4 连接级 LB,长连接焊死后端改 L7(请求级)或客户端侧 LB
扩容后新后端长期无流量老长连接不重连触发客户端重连 / 连接 drain / maxConnAge
后端日志里客户端 IP 全是 LB IPL4 加不了 XFF启用 PROXY protocol(两端都配)
配了 PROXY protocol 后端 400/协议错后端没开 PROXY protocol 接收后端配 proxy_protocol/accept-proxy
UDP 代理偶发丢响应会话超时太短,映射被提前回收调大 UDP 超时
后端挂了 LB 没摘除只有端口健康检查,应用假死查不到上 L7 应用层健康检查
DR 模式不通后端没配 VIP / 跨了二层检查 DR 组网约束

本章小结

  • L4 是连接级负载均衡:连接建立时选一次后端、整条焊死,长连接(gRPC)会负载不均——这是 L4/L7 选型的头号判据。
  • 数据路径两流派:代理模式(双连接、全流量过 LB)与 LVS 三模式(NAT/DR/TUN,DR 让响应绕开 LB 即 DSR)。
  • UDP 代理靠四元组建"伪会话",只能用超时回收。
  • L4 健康检查只能查端口;真实客户端 IP 靠 PROXY protocol 找回(两端都要支持)。

下一章 第10章 L7 代理,我们爬到应用层,看请求级路由、内容改写、应用健康检查这些 L4 永远做不到的能力,以及它们的代价。

Next
第10章 L7 代理:协议感知与基于内容的路由