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

学习目标

  • 掌握 L7"请求级"路由相对 L4"连接级"的能力跃迁与代价
  • 理解基于内容路由的维度(Host/Path/Header/Method/Cookie)与匹配优先级陷阱
  • 搞清 L7 的能力全家桶:改写、应用健康检查、重试、sticky、缓冲
  • 明白重试为什么对非幂等请求危险,sticky 为什么比 L4 源哈希精确

前置知识


原理

请求级:L7 的根本能力

L7 代理把字节流完整解析成应用层消息(第02章),因此能在每个请求粒度上做决策——这是与 L4"连接级"最根本的区别:

一条 keep-alive 连接:
  请求1 GET /api/users   ──▶ 后端A(api 集群)
  请求2 GET /static/x.js ──▶ 后端B(静态集群)   ← 同一条连接,不同请求去不同后端
  请求3 POST /api/order  ──▶ 后端A

正因如此,L7 天然解决了 第09章 的"长连接负载不均"——它在请求级重新分发,gRPC/h2 的多个 stream 能打散到不同后端。

基于内容路由的维度

L7 能读到完整请求,于是可按多种维度路由:

维度例子典型用途
Hostapi.example.com vs web.example.com多站点/多服务
Path/api/* vs /static/*微服务拆分、动静分离
HeaderX-Canary: true、User-Agent灰度、设备分流
MethodGET 走缓存、POST 走主库读写分离
Query/Cookie?ver=2、session=...A/B 测试、会话保持

路由匹配的优先级陷阱

多条规则同时匹配时,谁优先?这是 L7 配置最常见的坑。一般规则:精确 > 前缀(长的优先)> 正则(按定义顺序)。Nginx 的 location 匹配顺序尤其反直觉:

location = /api/health { ... }   # ① 精确匹配,最高优先
location ^~ /api/static/ { ... } # ② 前缀且阻止后续正则
location ~ \.php$ { ... }        # ③ 正则,按顺序
location /api/ { ... }           # ④ 普通前缀,最长匹配优先
location / { ... }               # ⑤ 兜底

头号事故:以为 location /api/ 会拦住 /api/health,但若另有 location = /api/health 或正则先命中,路由就跑偏了。改路由必测全部样例 URL。

L7 能力全家桶(L4 都做不到)

一旦解析到应用层,能做的事爆炸式增长:

  • 改写:rewrite/redirect、改 Header、改 Path、加删 Cookie
  • 应用层健康检查:GET /healthz 看返回码/响应体,比 L4 的"端口通"精确得多
  • 重试:后端失败自动重试另一个后端(注意幂等性,见下)
  • 超时分级:连接超时、首字节超时、整体超时
  • 熔断/限流:错误率高就熔断、按 QPS 限流(第31章)
  • 缓存/压缩:缓存响应、gzip/br 压缩
  • 鉴权:JWT 校验、外部鉴权(auth_request)
  • 灰度:按 Header/权重把流量切给新版本
  • 可观测:每请求埋点、access log、分布式追踪注入(第30章)

这些正是 API 网关(apiGateway 手册)和 Service Mesh 数据面(第29章)的能力来源——它们本质都是"被编排的 L7 反向代理"。

upstream / cluster:后端组抽象

L7 代理把一组后端抽象成 upstream(Nginx)/ cluster(Envoy),绑定 LB 算法 + 健康检查:

upstream api {
   后端列表 + 权重
   LB 算法(rr/least-conn/ip-hash/一致性哈希)
   主动健康检查(/healthz)
   连接池/keepalive
}

第09章 的 L4 用"源 IP 哈希"做会话保持,但NAT 后大量用户共享一个出口 IP(公司、运营商),源哈希会把他们全压到一个后端。L7 能种一个 cookie 标识后端,精确到用户:

首次请求 → 选后端A → 响应里 Set-Cookie: SRV=a
后续请求带 Cookie: SRV=a → L7 直接送后端A

重试的危险:幂等性

L7 自动重试很香,但对非幂等请求是灾难:

POST /pay 扣款 → 后端处理成功但响应丢失/超时 → 代理重试 → 又扣一次!
  • GET/PUT/DELETE 幂等,可安全重试
  • POST 非幂等,默认不应重试,或只在"连接都没建立"这种确定没副作用时重试
  • Nginx proxy_next_upstream 默认不含 non_idempotent,别手贱打开

代价:L7 比 L4 重一个数量级

能力的另一面是开销:

  • 要完整解析+缓冲应用报文(CPU、内存)
  • 每连接维护应用层状态
  • 改写、压缩、鉴权都吃 CPU
  • 不能像 L4 那样 splice 零拷贝(第12章)——数据必须进用户态

选型心法(呼应 第02章):只在需要"看内容"时才上 L7。纯转发、极致性能用 L4;要路由/改写/治理才上 L7。很多架构是 L4(边缘抗量)+ L7(内层治理) 分层组合。


️ 实现 / 命令

实验一:Nginx L7 内容路由 + 动静分离 + 改写

upstream api_pool {
    least_conn;
    server 10.0.0.11:8080 max_fails=3 fail_timeout=10s;
    server 10.0.0.12:8080 max_fails=3 fail_timeout=10s;
    keepalive 32;                      # 到后端的连接池
}
server {
    listen 80;

    location = /healthz { return 200 "ok\n"; }      # 精确匹配优先

    location /api/ {
        proxy_pass http://api_pool;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_next_upstream error timeout;           # 只对安全情况重试,不含 non_idempotent
    }

    location /static/ {
        proxy_pass http://10.0.0.20:8081;            # 静态走另一组
        proxy_cache_valid 200 10m;                   # L7 才能做的缓存
    }

    rewrite ^/old/(.*)$ /new/$1 permanent;           # L7 才能做的路径改写
}

实验二:按 Header 做灰度(金丝雀)

# 带 X-Canary: true 的请求进新版本,其余进稳定版
map $http_x_canary $pool {
    "true"  canary_pool;
    default stable_pool;
}
server {
    listen 80;
    location / { proxy_pass http://$pool; }
}
curl -H "X-Canary: true" http://localhost/   # → 新版本
curl http://localhost/                        # → 稳定版

这种"按请求内容切流量"是 L4 永远做不到的,也是灰度发布的基础(详见 apiGateway · 灰度发布)。

upstream app {
    server 10.0.0.11:8080;
    server 10.0.0.12:8080;
    sticky cookie SRV expires=1h;     # 商业版/第三方模块;开源可用 ip_hash 退而求其次
}

排错

现象根因解决
路由跑到错误的 location匹配优先级理解错按"精确>^~前缀>正则>前缀"核对,测全部样例 URL
POST 被重复执行(重复下单)对非幂等请求开了重试关闭 non_idempotent 重试
灰度 Header 不生效改写/map 顺序或缓存命中检查 map、绕过缓存验证
sticky 失效,会话乱跳用了 L4 源哈希且用户共享出口 IP改 L7 cookie sticky
改写后 301 循环rewrite 规则自我匹配加 break/精确条件
后端拿到的 Host 不对没设 proxy_set_header Host显式透传或改写 Host
大响应内存暴涨缓冲了大 body关 proxy_buffering 走流式(第03章)

本章小结

  • L7 在请求级决策,天然解决长连接负载不均,能按 Host/Path/Header/Method/Cookie 路由。
  • 路由优先级陷阱(精确>前缀>正则)是头号事故源,改路由必测全样例。
  • L7 能力全家桶(改写/应用健康检查/重试/sticky/缓存/灰度/可观测)是 API 网关与 Mesh 数据面的根基。
  • 重试要看幂等性(POST 别重试);cookie sticky 比 L4 源哈希精确;代价是重一个数量级、不能零拷贝。

下一章 第11章 透明代理,回到内核——客户端零配置时,iptables/TPROXY/eBPF 是怎么把流量"拐"进代理的,这也是 Istio Sidecar 零侵入劫持的底层原理。