第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 IP | L4 加不了 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 永远做不到的能力,以及它们的代价。