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 文档写明:
- Decoder
过滤器按配置顺序调用:
A → B → C。 - Encoder
过滤器按逆序调用:
C → B → A。 - 最后一个配置的过滤器必须是 terminal
filter;是否终端由
NamedHttpFilterConfigFactory::isTerminalFilterByProto判定,实践中几乎总是 Router(envoy.filters.http.router)。
因此:写在 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。
关键不变量:
- HCM 在 filter 链开始时会缓存一条
route(cached route)。更早的 filter 可以
clearRouteCache()、setRoute()或refreshRouteCluster()改写目标。 - 到达 Router 之后,这条选择被 finalize;后续不再指望「再过一个 filter 改 cluster」。
- 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
timeout、x-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 重试条件与预算
- 路由 / VirtualHost 的
retry_policy,或头x-envoy-retry-on/x-envoy-retry-grpc-on、x-envoy-max-retries。 - 未配置时,默认不重试(官方明确写死)。
- 重试还会撞上 cluster 的 max retries
熔断 与 retry budget;溢出时计数
upstream_rq_retry_overflow,并可能打上x-envoy-overloaded。细节见 第 8 篇。
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)
- Envoy Proxy v1.39.0,HTTP filters(Filter ordering、route mutation、terminal filter)。
- Envoy Proxy v1.39.0,HTTP routing(Retry semantics、Request hedging、Priority routing)。
- Envoy Proxy v1.39.0,Router
过滤器配置参考(超时头、
x-envoy-overloaded、统计项)。 - Envoy Proxy v1.39.0,Life of a Request(Router finalize route、连接池索取)。
源码(A)
envoyproxy/envoytag v1.39.0:source/common/router/、source/extensions/filters/http/router/;terminal 判定见 HTTP filter 工厂接口。
站内
实验台账
- 本篇无命令输出、无 QPS / 延迟数字。
七、小结
- Decoder 顺序、Encoder 逆序;链尾必须是 terminal filter,实践中即 Router。
- Router 终局选 cluster
并索取连接池;下游
http_filters不能接在 Router 后面继续「再过滤再转发」。 - 重试与超时挂在 Route/VH + Router;总超时包含重试,默认不重试,预算与熔断决定会否变成 503 / overloaded。
- 选对 cluster 之后,主机如何挑、池与熔断如何卡死——见 第 7–8 篇。
与 第 1 篇 请求路径轴对齐:本篇覆盖「HTTP filters → Router」;编解码之前见第 5 篇,上游池之后见第 7–8 篇。
→ 系列目录 · 上一篇:HCM 与 Codec · 下一篇:Cluster 与负载均衡
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Envoy 数据面】Network filter 与 Connection:字节流、水印与 TCP Proxy / HCM 分叉
在 FilterChain 已选定之后,说明 network filter 如何在连接字节流上工作、读写水印如何反压、连接生命周期事件,以及 TCP Proxy 相对 HTTP Connection Manager 的路径分叉。
【Envoy 数据面】HCM 与 Codec:HTTP/1·2·3、流生命周期与编解码错误
说明 HttpConnectionManager 如何把字节变成协议无关的 stream 事件;HTTP/1/2/3 codec 的职责边界;流与连接生命周期差异;以及 codec 错误与半关闭语义的失败模式。
Envoy / 数据面代理内核:从 Listener 到 xDS
补齐站内 Envoy 单篇地图与 Mesh/网关选型之间的数据面内核层:Main/Worker、FilterChainMatch、HCM、Cluster/连接池、xDS warming 与 Hot restart,并以排障与选型收束。
【Envoy 数据面】数据面全景:从 Listener 到 xDS 的可编程代理内核
定位 Envoy 相对 Nginx/HAProxy 静态配置代理与 Mesh 选型叙事的生态位;钉住请求路径、配置快照、FilterChainMatch、xDS warming、上游资源五条坐标系,并给出与 network/57 的分工及 16 篇阅读路线。