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

【Envoy 数据面】HTTP filter 链与 Router:顺序语义、终端过滤器与重试超时落点

文章导航

分类入口
networkproxy
标签入口
#envoy#http-filter#router#retry#timeout#http-connection-manager#filter-chain#v1.39

目录

HCM 与 Codec 把字节流变成 HTTP 流事件之后,请求还要穿过一串 HTTP filter,最后由 Router 选定 cluster、拿连接池、发往上游。运维里最常见的误配是:把鉴权 / 改头过滤器写在 envoy.filters.http.router 之后,或以为「配置了 retry 就会无限重试」。本文只钉三件事:链上顺序语义、Router 终端角色、重试与超时的附着点。

本文是「Envoy / 数据面代理内核」系列第 6 篇(共 16 篇)。→ 系列目录

篇目 核心内容
第 5 篇 · HCM / Codec 流与编解码
第 6 篇 · HTTP filter / Router 顺序、终端、retry/timeout
第 7 篇 · Cluster / LB 上游选主与优先级

上一篇HCM 与 Codec · 下一篇Cluster 与负载均衡

版本锚定:Envoy v1.39.0 官方 HTTP filters / HTTP routing / Router 配置参考与同 tag 源码树。本篇无压测数字。

本篇不展开:Listener / FilterChainMatch(第 3 篇)、Network filter(第 4 篇)、具体 ext_authz / Wasm 扩展实现(第 13 篇)。只回答「链上谁先谁后、谁终端、retry/timeout 挂在哪」。


一、问题:过滤器写了却不生效

HTTP Connection Manager(HCM)的 http_filters 是一份有序列表。官方 HTTP filters 文档写明:

因此:写在 Router 之后的过滤器,既不会出现在 decoder 路径上,也不会参与响应编码——配置可能通过校验失败,或在工厂校验路径上被拒绝;即便勉强加载,语义上也不属于「Router 之后再加工再转发」的管道模型。常见症状是「Lua / ext_authz / header 改写怎么都不跑」,而 config_dump 里 Router 已是链尾。

flowchart LR
  codec["Codec decode"] --> A["Filter A decode"]
  A --> B["Filter B decode"]
  B --> R["Router decode<br/>select cluster"]
  R --> up["Upstream"]
  up --> Renc["Router encode"]
  Renc --> Benc["Filter B encode"]
  Benc --> Aenc["Filter A encode"]
  Aenc --> out["Codec encode"]

二、Router 终端:路由终局与上游入口

Life of a Request(v1.39 文档树)把 Router 钉在链尾:当 Router 的 decodeHeaders 被调用时,路由选择终局,cluster 名确定,随后向 ClusterManager 索取该 cluster 的 HTTP 连接池,再把请求发往选中的 endpoint。

关键不变量:

  1. HCM 在 filter 链开始时会缓存一条 route(cached route)。更早的 filter 可以 clearRouteCache()setRoute()refreshRouteCluster() 改写目标。
  2. 到达 Router 之后,这条选择被 finalize;后续不再指望「再过一个 filter 改 cluster」。
  3. Router 还负责创建并驱动 Upstream HTTP filter 链(挂在 cluster 上,默认在 headers 到达 Router 后开始)。那是另一条链,不是把下游 http_filters 续写到 Router 后面。

安全注意(官方 Filter route mutation):若鉴权类 filter(RBAC / ExtAuthz / JWT)依赖「当前 route」,而其后的 Lua / ext_proc 等又 clearRouteCache(),则鉴权看到的 route 与最终 Router 看到的可能不一致。原则是:会改 route-matching 输入的过滤器尽量放在依赖该决策的授权过滤器之前,或禁止不可信来源清缓存。

2.1 每路由开关与上游链

typed_per_filter_config 可按 virtual host / route 禁用链上某个下游 filter,或给默认 disabled: true 的 filter 做 per-route 启用。这改变的是「同一次请求上哪些 decoder/encoder 参与」,不改变「Router 必须仍是 HCM http_filters 终端」这一约束。

Upstream HTTP filter 挂在 cluster 配置上,由 Router 在选定上游之后驱动:例如镜像流量可以对主集群与 shadow 集群使用不同上游链。排障时若只改了 Listener 侧 http_filters 却期望改写「已出 Router 之后」的上游请求形态,应检查 cluster 级 upstream filter,而不是在 Router 后再追加下游 filter。


三、重试与超时挂在哪里

重试与超时不是随意某个 HTTP filter 的私货;语义落在 Route / VirtualHost 的 policy,由 Router 执行,并可被请求头覆盖。

3.1 超时两层

旋钮 含义 典型来源
路由 / 请求总超时 含所有重试在内的外层时限 route timeoutx-envoy-upstream-rq-timeout-ms
Per-try 超时 单次上游尝试时限 x-envoy-upstream-rq-per-try-timeout-ms、route retry/hedge 相关配置

官方 Router 文档强调:所有重试都包含在总超时内。总超时 3s、首次尝试耗掉 2.7s,则剩余重试(含 backoff)只剩约 0.3s。这是刻意设计,用来避免「重试次数 × 单次超时」指数爆炸。

超时本身(504)默认不触发 5xx 类重试条件里「等总超时」那条路径;若要对「单次太慢」重试,应使用 per-try 超时,而不是指望总超时后再重试。

3.2 重试条件与预算

Backoff 默认全抖动指数:基间隔 \(B\)(runtime upstream.base_retry_backoff_ms,默认 25ms),第 \(N\) 次重试落在 \([0,(2^N-1)B)\),有上限。也可按上游 Retry-After 等头做 rate-limited backoff。

3.3 Hedging

Hedge policy 允许在 per-try 超时 时再发一次上游请求而不取消原请求,取首个「按 retry 策略可接受」的响应。官方限制:当前 hedging 仅对 per-try 超时路径实现,且保证同一上游请求不会因「超时后又 5xx」被重复计成两次独立可重试事件。


四、配置骨架与常见误配

http_filters:
  - name: envoy.filters.http.ext_authz
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
      # ...
  - name: envoy.filters.http.router
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
误配 现象 纠正
Filter 写在 Router 之后 改头 / 鉴权从不执行 Router 必须是 http_filters 最后一项
只配 retry_on 不配超时边界 尾延迟被重试拖长 同时收紧总超时与 per-try;配 retry budget
对非幂等写操作开 5xx 重试 重复写 按语义选 reset-before-request / 收窄条件
授权 filter 在清 route cache 的 filter 之前 授权与实际路由不一致 调整顺序或禁止清缓存

Router 还产出 http.<stat_prefix>.no_route(404)、no_cluster(默认 503)等计数——路由表缺条目与「cluster 名不存在」是两条失败轴,不要和上游 503 混为一谈。

路由表本身由 HCM 持有(静态 route_config 或 RDS)。HCM 保证对同一请求多次取 route 的结果稳定(含 runtime 随机分流)。Rate limit 等过滤器也会读这张表,但只有 Router 负责真正转发;其他 filter 「看见路由」不等于「已经出站」。


五、谱系、争论与开放问题

谱系:可编程 L7 代理把「匹配 → 策略 filter → 转发」拆成可组合阶段;Envoy 把转发收束为 terminal Router,与「一切皆中间件、任意位置都能 proxy_pass」的模型不同。重试语义更接近应用层幂等约定 + 代理侧预算,而不是传输层自动重传。

争论:是否应在数据面默认开启激进重试。A 侧强调掩盖瞬时故障;B 侧(含官方 circuit breaking / retry budget 叙事)强调无预算重试会放大级联故障。本系列立场:重试是显式、有预算的策略,默认关闭是合理基线。

开放问题:Upstream HTTP filter 与 Downstream 链的观测对齐(同一请求两套链上的 span / access log 字段如何统一)仍依赖部署约定;完整扩展落点见第 13 篇。


六、参考资料

规范 / 官方文档(A)

源码(A)

站内

实验台账


七、小结

  1. Decoder 顺序、Encoder 逆序;链尾必须是 terminal filter,实践中即 Router。
  2. Router 终局选 cluster 并索取连接池;下游 http_filters 不能接在 Router 后面继续「再过滤再转发」。
  3. 重试与超时挂在 Route/VH + Router;总超时包含重试,默认不重试,预算与熔断决定会否变成 503 / overloaded。
  4. 选对 cluster 之后,主机如何挑、池与熔断如何卡死——见 第 7–8 篇

第 1 篇 请求路径轴对齐:本篇覆盖「HTTP filters → Router」;编解码之前见第 5 篇,上游池之后见第 7–8 篇。

系列目录 · 上一篇:HCM 与 Codec · 下一篇:Cluster 与负载均衡

同主题继续阅读

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

2026-08-10 · network / proxy

Envoy / 数据面代理内核:从 Listener 到 xDS

补齐站内 Envoy 单篇地图与 Mesh/网关选型之间的数据面内核层:Main/Worker、FilterChainMatch、HCM、Cluster/连接池、xDS warming 与 Hot restart,并以排障与选型收束。


By .