土法炼钢兴趣小组的算法知识备份

【网络工程】L7 负载均衡:HTTP 感知路由与内容交换

文章导航

分类入口
network
标签入口
#load-balancing#l7#reverse-proxy#http-routing#nginx

目录

L7 负载均衡工作在应用层,能够解析 HTTP 协议的完整语义——URL 路径、Host 头、Cookie、HTTP 方法、请求体。这让它拥有 L4 负载均衡不可能具备的能力:基于内容的路由决策。

但这种能力是有代价的。L7 LB 必须终止 TCP 连接、解析 HTTP 报文,每个请求都产生计算开销。理解这个代价,才能在 L4 和 L7 之间做出正确的架构决策。

一、L7 与 L4 的本质区别

1.1 调度粒度

L4 和 L7 最根本的区别在于调度粒度:

维度 L4 负载均衡 L7 负载均衡
调度单位 TCP 连接 / UDP 数据报 HTTP 请求
协议感知 仅 IP + Port HTTP 完整语义
路由依据 源/目标 IP、端口 URL、Host、Header、Cookie
TLS 处理 透传(不解密) 终止(必须解密)
连接模型 1:1(客户端↔︎后端) N:M(连接复用)
性能 百万级 CPS 十万级 RPS
典型延迟增加 <0.1ms 0.5-2ms

一个关键区别是连接模型。L4 是连接级转发——客户端的一个 TCP 连接对应后端的一个 TCP 连接,连接建立后所有数据包都转发给同一后端。L7 是请求级转发——同一个客户端连接上的不同 HTTP 请求可以被路由到不同的后端:

L4 模式:
Client ──TCP──→ L4 LB ──TCP──→ Backend A
                               (整个连接的所有请求都去 A)

L7 模式:
Client ──TCP──→ L7 LB ──→ GET /api  → Backend A
                       ──→ GET /img  → Backend B  (CDN 节点)
                       ──→ POST /api → Backend C  (写入节点)

1.2 L7 LB 的连接复用

L7 LB 维护两组连接池——前端连接(面向客户端)和后端连接(面向 RS)。后端连接可以被复用:

                    前端连接池                后端连接池
Client A ──HTTP/1.1──┐                 ┌──keep-alive──→ Backend 1
Client B ──HTTP/1.1──┼──→ L7 LB ──────┤
Client C ──HTTP/2────┤   (请求级路由)   ├──keep-alive──→ Backend 2
Client D ──HTTP/1.1──┘                 └──keep-alive──→ Backend 3

连接复用的工程意义:

# Nginx 后端连接池配置
upstream backend {
    server 10.0.2.10:8080;
    server 10.0.2.11:8080;
    server 10.0.2.12:8080;

    keepalive 64;              # 每个 worker 保持 64 个空闲后端连接
    keepalive_requests 1000;   # 每个连接最多处理 1000 个请求后关闭
    keepalive_timeout 60s;     # 空闲连接超时 60 秒
}

server {
    listen 80;

    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;                    # 后端使用 HTTP/1.1
        proxy_set_header Connection "";            # 启用 keep-alive
        proxy_set_header Host $host;
    }
}

二、HTTP 感知路由

2.1 基于 Host 的路由

最基础的 L7 路由——根据 HTTP Host 头将请求分发到不同的后端服务:

# Nginx 基于 Host 的虚拟主机路由
server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://api_backend;
    }
}

server {
    listen 80;
    server_name www.example.com;

    location / {
        proxy_pass http://web_backend;
    }
}

server {
    listen 80;
    server_name admin.example.com;

    # 管理后台限制来源 IP
    allow 10.0.0.0/8;
    deny all;

    location / {
        proxy_pass http://admin_backend;
    }
}
# HAProxy 基于 Host 的路由
frontend http_front
    bind *:80
    mode http

    # 根据 Host 头选择后端
    acl is_api hdr(host) -i api.example.com
    acl is_web hdr(host) -i www.example.com
    acl is_admin hdr(host) -i admin.example.com

    use_backend api_back if is_api
    use_backend web_back if is_web
    use_backend admin_back if is_admin
    default_backend web_back

backend api_back
    mode http
    balance roundrobin
    server api1 10.0.2.10:8080 check
    server api2 10.0.2.11:8080 check

backend web_back
    mode http
    balance roundrobin
    server web1 10.0.3.10:80 check
    server web2 10.0.3.11:80 check

2.2 基于 Path 的路由

将不同 URL 路径路由到不同的后端服务——这是微服务架构中最常见的 L7 路由模式:

# Nginx 基于路径的路由
upstream user_service {
    server 10.0.2.10:8080;
    server 10.0.2.11:8080;
}

upstream order_service {
    server 10.0.3.10:8080;
    server 10.0.3.11:8080;
}

upstream static_assets {
    server 10.0.4.10:80;
}

server {
    listen 80;
    server_name api.example.com;

    # 精确匹配
    location = /health {
        return 200 "OK";
    }

    # 前缀匹配
    location /api/v1/users {
        proxy_pass http://user_service;
        proxy_set_header X-Request-ID $request_id;
    }

    location /api/v1/orders {
        proxy_pass http://order_service;
        proxy_set_header X-Request-ID $request_id;
    }

    # 正则匹配静态资源
    location ~* \.(jpg|jpeg|png|gif|css|js|ico|svg)$ {
        proxy_pass http://static_assets;
        proxy_cache_valid 200 1h;
        add_header Cache-Control "public, max-age=3600";
    }

    # 默认路由
    location / {
        return 404 '{"error": "not found"}';
    }
}

Nginx location 匹配优先级(从高到低):

优先级 语法 说明 示例
1 = /path 精确匹配 = /health
2 ^~ /path 前缀匹配(不检查正则) ^~ /static/
3 ~ regex 正则匹配(区分大小写) ~ \.php$
3 ~* regex 正则匹配(不区分大小写) ~* \.(jpg|png)$
4 /path 普通前缀匹配 /api/

2.3 基于 Header 的高级路由

利用 HTTP 头实现灰度发布、A/B 测试、多版本共存:

# Nginx 基于 Header 的灰度路由
upstream stable {
    server 10.0.2.10:8080;
    server 10.0.2.11:8080;
}

upstream canary {
    server 10.0.3.10:8080;
}

# 使用 map 做路由决策
map $http_x_canary $backend {
    "true"    canary;
    default   stable;
}

# 基于 Cookie 做百分比灰度
split_clients $request_id $canary_pool {
    5%     canary;       # 5% 流量走灰度
    *      stable;       # 95% 走稳定版
}

server {
    listen 80;

    location /api/ {
        # 优先检查 Header,其次用百分比灰度
        set $target $canary_pool;
        if ($http_x_canary = "true") {
            set $target canary;
        }
        proxy_pass http://$target;
    }
}
# HAProxy 基于 Header 的路由
frontend http_front
    bind *:80
    mode http

    # 基于 Header 路由
    acl is_canary hdr(X-Canary) -i true
    acl is_beta hdr(X-Version) -i beta
    acl is_mobile hdr_sub(User-Agent) -i mobile

    # 基于 Cookie 路由
    acl has_session_a cook(session_pool) -m str pool_a
    acl has_session_b cook(session_pool) -m str pool_b

    use_backend canary_back if is_canary
    use_backend beta_back if is_beta
    use_backend mobile_back if is_mobile
    use_backend pool_a_back if has_session_a
    use_backend pool_b_back if has_session_b
    default_backend stable_back

2.4 基于请求方法的路由

将读写请求分离到不同的后端——这在数据库读写分离场景中很有用:

# 读写分离路由
upstream read_backends {
    server 10.0.2.10:8080;    # 读副本 1
    server 10.0.2.11:8080;    # 读副本 2
    server 10.0.2.12:8080;    # 读副本 3
}

upstream write_backends {
    server 10.0.3.10:8080;    # 主节点
    server 10.0.3.11:8080 backup;  # 备用主节点
}

server {
    listen 80;

    location /api/ {
        # GET/HEAD 走读副本
        if ($request_method ~* ^(GET|HEAD)$) {
            proxy_pass http://read_backends;
        }

        # POST/PUT/DELETE/PATCH 走主节点
        proxy_pass http://write_backends;
    }
}

三、SSL Termination 架构

3.1 三种 TLS 处理模式

L7 LB 的一个核心功能是 SSL/TLS 终止。三种模式各有适用场景:

模式一:TLS Termination(终止)

Client ──HTTPS──→ L7 LB ──HTTP──→ Backend
                  (解密)          (明文)
server {
    listen 443 ssl http2;
    server_name api.example.com;

    ssl_certificate     /etc/ssl/certs/api.example.com.pem;
    ssl_certificate_key /etc/ssl/private/api.example.com.key;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers on;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 1h;
    ssl_stapling on;
    ssl_stapling_verify on;

    location / {
        proxy_pass http://backend;    # 后端使用 HTTP
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

模式二:TLS Passthrough(透传)

Client ──HTTPS──→ L4 LB ──HTTPS──→ Backend
                 (不解密)           (自己解密)
# HAProxy TLS 透传
frontend tls_passthrough
    bind *:443
    mode tcp
    tcp-request inspect-delay 5s
    tcp-request content accept if { req_ssl_hello_type 1 }

    # 基于 SNI 路由(不解密,只读 SNI)
    use_backend api_tls if { req_ssl_sni -i api.example.com }
    use_backend web_tls if { req_ssl_sni -i www.example.com }

backend api_tls
    mode tcp
    server api1 10.0.2.10:443 check

模式三:TLS Re-encryption(重加密)

Client ──HTTPS──→ L7 LB ──HTTPS──→ Backend
                  (解密+重加密)     (再次解密)
server {
    listen 443 ssl;
    server_name api.example.com;

    ssl_certificate     /etc/ssl/certs/frontend.pem;
    ssl_certificate_key /etc/ssl/private/frontend.key;

    location / {
        proxy_pass https://backend;    # 后端也使用 HTTPS
        proxy_ssl_certificate     /etc/ssl/certs/client.pem;    # mTLS
        proxy_ssl_certificate_key /etc/ssl/private/client.key;
        proxy_ssl_verify on;
        proxy_ssl_trusted_certificate /etc/ssl/certs/ca.pem;
    }
}

3.2 三种模式的选型

模式 安全性 性能 复杂度 适用场景
Termination LB↔︎Backend 明文 最好(后端免 TLS) 内网可信环境
Passthrough 端到端加密 最好(LB 不解密) 合规要求端到端加密
Re-encryption LB↔︎Backend 加密 最差(两次 TLS) 零信任网络

生产环境常见选择:

四、L4 + L7 混合架构

4.1 典型的双层架构

生产环境通常不会只用 L4 或只用 L7,而是两层配合:

                    ┌─────────────────────────────────┐
                    │         互联网 / 客户端           │
                    └──────────────┬──────────────────┘
                                   │
                    ┌──────────────┴──────────────────┐
                    │     L4 负载均衡 (LVS / NLB)      │
                    │  • 连接级调度                     │
                    │  • DDoS 防护                     │
                    │  • 高吞吐量                      │
                    │  • VIP 高可用                     │
                    └──────┬──────────────┬───────────┘
                           │              │
                    ┌──────┴─────┐ ┌──────┴──────┐
                    │ L7 LB #1   │ │ L7 LB #2    │
                    │ Nginx/     │ │ Nginx/      │
                    │ Envoy      │ │ Envoy       │
                    │ • TLS 终止 │ │ • TLS 终止  │
                    │ • HTTP路由 │ │ • HTTP路由  │
                    │ • 限流/WAF │ │ • 限流/WAF  │
                    └──┬──┬──┬──┘ └──┬──┬──┬────┘
                       │  │  │       │  │  │
                ┌──────┘  │  └───┐   │  │  │
                ↓         ↓      ↓   ↓  ↓  ↓
            ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
            │ App1 │ │ App2 │ │ App3 │ │ App4 │
            └──────┘ └──────┘ └──────┘ └──────┘

4.2 各层职责

层级 职责 不做什么
L4 LB 连接调度、VIP 管理、DDoS 吸收 不解析 HTTP、不终止 TLS
L7 LB TLS 终止、HTTP 路由、限流、WAF 不做连接级高可用(由 L4 保证)
应用 业务逻辑 不直接暴露到公网

这种分层的好处:

4.3 DSR + L7 的高性能架构

对于极高流量场景,可以在 L7 LB 上也使用 DSR(Direct Server Return)思路——L7 LB 的响应直接发给客户端,不回经 L4 LB:

# L4 使用 DR 模式
# L7 LB 节点配置 VIP 在 lo 上 + ARP 抑制
# 这样 L7 的响应直接从 L7 节点发出,不回经 L4

# L7 LB 节点上
ip addr add 10.0.1.100/32 dev lo
sysctl -w net.ipv4.conf.lo.arp_ignore=1
sysctl -w net.ipv4.conf.lo.arp_announce=2

4.4 何时不需要 L4 层

并非所有场景都需要双层架构。以下情况可以只用 L7:

反过来,以下场景需要双层:

五、L7 负载均衡性能优化

5.1 连接与缓冲优化

# Nginx 高性能配置
worker_processes auto;                     # 自动匹配 CPU 核数
worker_rlimit_nofile 65535;               # 文件描述符限制

events {
    worker_connections 16384;              # 每个 worker 的最大连接数
    use epoll;                            # 使用 epoll
    multi_accept on;                      # 一次 accept 多个连接
}

http {
    # 连接优化
    keepalive_timeout 65;                  # 客户端 keep-alive 超时
    keepalive_requests 1000;              # 每个连接最大请求数
    reset_timedout_connection on;         # 超时连接直接 RST

    # 缓冲优化
    proxy_buffering on;
    proxy_buffer_size 4k;                 # 响应头缓冲
    proxy_buffers 8 16k;                  # 响应体缓冲
    proxy_busy_buffers_size 32k;
    proxy_temp_file_write_size 64k;

    # 超时设置
    proxy_connect_timeout 5s;             # 连接后端超时
    proxy_send_timeout 30s;               # 发送到后端超时
    proxy_read_timeout 60s;               # 读取后端响应超时

    # 请求体限制
    client_max_body_size 10m;
    client_body_buffer_size 128k;
}

5.2 性能对比:各 L7 LB 的吞吐量

不同 L7 LB 在典型配置下的性能参考(单核,HTTP/1.1,小响应体):

L7 LB RPS (单核) P99 延迟 内存占用 适用场景
Nginx 30,000-50,000 1-2ms 通用 Web 服务
HAProxy 40,000-60,000 0.5-1ms 高性能 TCP/HTTP
Envoy 20,000-35,000 1-3ms Service Mesh
Traefik 15,000-25,000 2-5ms 动态配置/K8s

这些数字高度依赖具体配置、后端响应时间和网络条件。实际部署前必须在自己的环境中做基准测试。

5.3 L7 LB 的瓶颈定位

# 1. 检查 CPU 瓶颈
top -p $(pgrep -d, nginx)
# 如果 Nginx worker CPU 接近 100%,说明 L7 处理是瓶颈

# 2. 检查连接数
ss -s
# Total: 23456 (kernel 24000)
# TCP:   12345 (estab 10000, closed 234, orphaned 12, ...)

# 3. 检查文件描述符
ls /proc/$(pgrep -o nginx)/fd | wc -l
cat /proc/$(pgrep -o nginx)/limits | grep "open files"

# 4. 检查后端连接池状态(Nginx stub_status)
# 需要编译时包含 --with-http_stub_status_module
curl http://localhost/nginx_status
# Active connections: 1234
# server accepts handled requests
#  56789 56789 123456
# Reading: 12 Writing: 34 Waiting: 1188

# 5. 检查 error log 中的 upstream 错误
grep "upstream" /var/log/nginx/error.log | tail -20
# upstream timed out (110: Connection timed out)
# no live upstreams while connecting to upstream
# upstream prematurely closed connection

六、实战:从 L4 到 L7 的完整请求路径

用一个具体的例子追踪请求从客户端到后端的完整路径:

客户端发起请求: curl https://api.example.com/users/123

1. DNS 解析
   api.example.com → 203.0.113.100 (VIP)

2. TCP 握手(到 L4 LB 的 VIP)
   Client:43210 → 203.0.113.100:443 [SYN]
   ← [SYN-ACK]
   → [ACK]

3. L4 LB 调度(IPVS DR 模式)
   IPVS 选择 L7 LB 节点: 10.0.2.10
   修改 MAC 地址,转发到 10.0.2.10

4. TLS 握手(在 L7 LB 上)
   ClientHello → ServerHello → Certificate → ...
   L7 LB 使用自己的证书完成 TLS 握手

5. HTTP 请求到达 L7 LB
   GET /users/123 HTTP/1.1
   Host: api.example.com

6. L7 LB 路由决策
   path=/users/* → upstream user_service
   算法=wlc → 选择 10.0.3.11:8080

7. L7 LB 转发(使用后端连接池)
   从 keepalive 池中取出到 10.0.3.11 的连接
   GET /users/123 HTTP/1.1
   Host: api.example.com
   X-Real-IP: 客户端真实IP
   X-Request-ID: abc-123-def

8. 后端处理并响应
   HTTP/1.1 200 OK
   Content-Type: application/json
   {"id": 123, "name": "alice"}

9. L7 LB 转发响应给客户端
   通过已建立的 TLS 连接发回
   (DR 模式下,响应直接从 L7 LB 发出,不经过 L4 LB)
# 用 curl 观察完整耗时分解
curl -w "\
    DNS:        %{time_namelookup}s\n\
    TCP:        %{time_connect}s\n\
    TLS:        %{time_appconnect}s\n\
    TTFB:       %{time_starttransfer}s\n\
    Total:      %{time_total}s\n\
    Remote IP:  %{remote_ip}\n" \
    -o /dev/null -s https://api.example.com/users/123

# 示例输出:
# DNS:        0.012s      ← DNS 解析
# TCP:        0.025s      ← TCP 握手(到 L4 VIP)
# TLS:        0.058s      ← TLS 握手(在 L7 LB 上)
# TTFB:       0.089s      ← 首字节(含 L7 路由 + 后端处理)
# Total:      0.091s
# Remote IP:  203.0.113.100   ← L4 VIP

七、L7 LB 的常见陷阱

7.1 大请求体阻塞

当客户端上传大文件时,L7 LB 默认会先缓冲完整请求体,再转发给后端。这会增加延迟和内存消耗:

# 大文件上传场景:关闭 request body 缓冲
location /upload {
    proxy_pass http://upload_backend;
    proxy_request_buffering off;    # 边接收边转发
    client_max_body_size 1g;        # 允许 1GB 上传
}

7.2 WebSocket 升级

L7 LB 必须显式支持 WebSocket 的 HTTP Upgrade:

location /ws {
    proxy_pass http://ws_backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 3600s;      # WebSocket 连接需要长超时
}

7.3 健康检查与连接排空

L7 LB 在摘除后端节点时,必须优雅地排空已有连接:

upstream backend {
    server 10.0.2.10:8080 max_fails=3 fail_timeout=30s;
    server 10.0.2.11:8080 max_fails=3 fail_timeout=30s;
    # max_fails: 连续失败 3 次判定不健康
    # fail_timeout: 30 秒内的失败计数窗口,且不健康后 30 秒才重试
}
# HAProxy 的优雅排空
backend app_back
    option httpchk GET /health
    http-check expect status 200

    server app1 10.0.2.10:8080 check inter 5s fall 3 rise 2
    server app2 10.0.2.11:8080 check inter 5s fall 3 rise 2

    # 通过 Runtime API 排空
    # echo "set server app_back/app1 state drain" | socat stdio /var/run/haproxy.sock
    # drain: 不接受新连接,已有连接继续处理
    # maint: 立即摘除(不推荐)

7.4 X-Forwarded-For 信任链

L7 LB 会在请求中添加 X-Forwarded-For 头,但如果前面有多层代理,这个头可能被伪造:

# 设置可信代理列表
set_real_ip_from 10.0.0.0/8;       # L4 LB 内网段
set_real_ip_from 172.16.0.0/12;    # CDN 内网段
real_ip_header X-Forwarded-For;
real_ip_recursive on;               # 递归查找第一个不可信的 IP

八、故障排查实战

8.1 502 Bad Gateway

502 是 L7 LB 最常见的错误——表示 LB 无法从后端获得有效响应:

# 常见原因及排查
# 原因 1:后端服务宕机
curl -v http://10.0.2.10:8080/health
# 直接请求后端,确认是否存活

# 原因 2:后端响应超时
# 查看 Nginx error log
grep "upstream timed out" /var/log/nginx/error.log
# 解决:增大 proxy_read_timeout 或优化后端性能

# 原因 3:后端返回无效 HTTP 响应
grep "upstream sent invalid header" /var/log/nginx/error.log
# 某些后端框架的 HTTP 响应不规范

# 原因 4:后端连接被拒绝
grep "connect() failed" /var/log/nginx/error.log
# 后端的 listen backlog 满了,或端口没监听
ss -tlnp | grep 8080  # 在后端机器上检查

8.2 504 Gateway Timeout

# 504 表示后端在超时时间内没有返回响应
# 排查步骤:
# 1. 确认超时配置
nginx -T | grep -E "proxy_(connect|read|send)_timeout"

# 2. 检查后端处理耗时
curl -w "TTFB: %{time_starttransfer}s\n" -o /dev/null -s http://10.0.2.10:8080/slow-api
# 如果 TTFB > proxy_read_timeout,就会 504

# 3. 检查是否是特定路径
grep "504" /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head
# 找出哪些 URL 最容易 504

8.3 连接泄漏

# 症状:后端连接数持续增长,最终耗尽
ss -tn state established | grep ":8080" | wc -l

# 检查 TIME_WAIT 堆积
ss -tn state time-wait | grep ":8080" | wc -l

# 如果 TIME_WAIT 很多,说明连接没有被复用
# 检查 keepalive 配置是否正确
# 常见错误:proxy_set_header Connection "close" 会禁用复用

九、总结

  1. L7 的核心价值是请求级路由。基于 Host/Path/Header/Cookie 的路由能力,让一个入口点服务多个微服务,这是 L4 做不到的。

  2. 连接复用是隐藏的性能杀手或性能利器。正确配置后端连接池(keepalive)可以极大减少后端的连接压力和 TLS 握手开销。配置不当则会导致连接泄漏或频繁重建。

  3. TLS Termination 是最常见的模式。除非合规要求端到端加密,否则在 L7 LB 上终止 TLS 是最简单高效的选择。后端加密的需求由 Service Mesh 处理。

  4. L4 + L7 双层架构是生产标配。L4 负责连接级高可用和 DDoS 防护,L7 负责请求级路由和应用层功能。不要试图用单层解决所有问题。

  5. 灰度发布在 L7 层实现最自然。基于 Header 或百分比的流量分割,L7 LB 原生支持,不需要额外组件。

  6. 监控 502/504 是 L7 LB 运维的核心。502 说明后端不可达,504 说明后端太慢。两者的排查路径完全不同——502 查连通性和端口监听,504 查后端处理性能和超时配置。建立 error rate 告警是上线前的必备项。


参考文献


上一篇:L4 负载均衡:IPVS、LVS 与连接级调度

下一篇:负载均衡算法深度解析:从轮询到 P2C

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2025-08-31 · network

【网络工程】HAProxy 工程:高级配置、ACL 与运维

系统讲解 HAProxy 的工程实践:Frontend/Backend/Listen 配置模型、ACL 规则引擎的高级用法、Runtime API 的动态管理能力、多线程模型与性能调优、SSL 终止与健康检查策略,建立 HAProxy 从配置到生产运维的完整体系。


By .