上一篇把连接送进某条 FilterChain。本篇问:链上的 network filter 看见的是什么? 它们操作的是原始字节与少量连接事件,而不是 HTTP header map。把 L4 与 L7 入口混为一谈,会在排障时从 Router 找「TCP 断连」原因。
本文依据 v1.39.0 Listener filters 文中的 Network (L3/L4) filters 与 TCP proxy filter 叙述,钉住 filter 类型、连接生命周期、缓冲水印反压,以及 TCP Proxy vs HCM 分叉。
本文是「Envoy / 数据面代理内核」系列第 4 篇(共 16 篇)。→ 系列目录
篇目 核心内容 第 3 篇 · Listener Match 选哪条链 第 4 篇 · Network Filter 字节流、水印、TCP Proxy / HCM 第 5 篇 · HCM Codec HTTP 事件与编解码 第 8 篇 · Conn Pool 上游连接资源限额
版本锚定:Envoy v1.39.0 Network (L3/L4) filters、TCP proxy filter;API v3
TcpProxy/HttpConnectionManager;源码source/common/network/、source/extensions/filters/network/。
一、Network filter:三类与组合语义
官方把 network filters 写成连接处理的核心:同一 listener
下不同 filter_chain
可挂不同有序列表。类型三分:
| 类型 | 触发 | 典型用途 |
|---|---|---|
| Read | 下游数据到达 | 解析、限流、检测 |
| Write | 即将写回下游 | 改写、计量 |
| Read/Write | 双向 | TLS、代理、HCM |
过滤器可 stop / continue 迭代,以便同步或异步调用外部服务后再放行。同一下游连接的 network filters 之间可共享静态与动态状态(官方 data sharing between filters)——状态作用域是单连接,不是跨 Worker 全局。
这与 HTTP filter 的关键差别:输入是字节与连接事件(TLS 握手完成、本地/远端断开等),不是「headers received」。HCM 本身是一个 network filter:它把字节翻译成 HTTP 事件,再把控制交给 HTTP filter 链。
二、Connection 生命周期(数据面视角)
stateDiagram-v2
[*] --> Accepted: accept and match chain
Accepted --> FilterInit: instantiate filters
FilterInit --> Open: onNewConnection
Open --> Reading: downstream data
Open --> Writing: upstream or local write
Reading --> Open
Writing --> Open
Open --> Closing: local or remote close
Closing --> [*]: filters torn down
简化阶段(机制叙述,非逐函数源码 trace):
- 实例化:按选中 chain 创建 filter 栈与
Connection上下文。 - 数据面循环:读事件 → Read 过滤器;上游或本地产生写 → Write 过滤器;事件回调在绑定该连接的 Worker 上执行(见第 2 篇)。
- 半关闭与整关闭:TCP 半关闭、reset、空闲超时、filter 主动关连接,都会驱动栈拆除;具体超时字段因 filter(TcpProxy vs HCM)而异。
- 与上游的关系:TCP Proxy 维持近似 1:1 的下游—上游连接;HCM 则在请求级向 cluster 借连接池中的上游连接(池化细节第 8 篇)。
失败模式:在 network 层关闭连接,access log / HTTP 统计可能从未看到「请求」——因为请求语义尚未诞生。TLS 握手失败、idle timeout、下游 reset,都会在进入 HCM 之前终止——排障清单应先问「连接是否活到过 network filter 的 onNewConnection」。
同一 Worker 上其他连接共享事件环:一条连接上的重 filter(例如同步调用外部授权)会推迟同线程其他连接的读写。这不是全局锁,但是线程级队头阻塞;与第 2 篇 Watchdog /「连接钉死 Worker」同根。
三、缓冲与水印:反压而不是无限队列
Envoy 连接带有读写缓冲。当缓冲超过高水位,连接进入 watermark 状态,通常停止继续读下游或通知过滤器「该减速」;落到低水位再恢复。目的是:慢消费者不能无限吃内存,并把压力传回对端或上游写路径。
本篇不下伪造阈值或「默认多少字节」——具体
per_connection_buffer_limit_bytes
等以当版配置与运行时为准。机制结论只需记住:
- 水印触发时,延迟上升可能来自缓冲反压,不是 CPU 算不过来;
- 巨型请求 / 慢上游 / 慢客户端组合会放大水位振荡;
- TCP Proxy 与 HCM 都受连接缓冲约束,但 HCM 还有流级与请求缓冲限制(下一篇)。
争论落点:用户态代理用显式水位模拟「背压」;内核 eBPF L4 转发往往不解析应用层,也没有同构的「HTTP 流水位」。选型时比较的是语义深度,不是抽象的「谁更快」。
与 overload / 连接限额的关系:水印是单连接的内存保险丝;cluster 连接上限与 listener connection limit 是资源配额。两者同时打满时,症状叠加(新连接拒、旧连接停读),归因要拆开看 stats 命名空间,而不是只盯一个 counter。
四、分叉 A:TCP Proxy
TCP proxy filter:下游与上游 cluster 之间做基本 1:1 连接代理,可单独作 stunnel 类用途,也可与 Mongo 嗅探、限流等 filter 组合。
关键不变量:
- 尊重 upstream cluster 全局资源管理器的连接上限;超限则不建上游连接(官方原文语义)。
- 不把流量解码成 HTTP stream;无 Router、无重试插件、无 RDS 路由表。
- 适合:TLS 穿透、原始 TCP、协议由更专用 filter 处理、或明确不需要 L7 策略。
排障时若链路配置的是 tcp_proxy,却在 HTTP
access log / :path
维度找原因——坐标系错了。连接上限打满时的表现是「下游能连上
Envoy,但建不起上游」——与 HCM 的 503
语义相邻但指标不同,应用 cluster 连接类计数区分。
五、分叉 B:通向 HCM 的路径
http_connection_manager 作为 network filter
挂在 chain 上时,后续世界切换为:
bytes → codec → HTTP streams → http_filters → Router → cluster pool
同一 listener 上可以一条链 TCP Proxy、另一条链 HCM(靠第 3 篇的 match)。「一切皆 HTTP filter」在工程上不成立:L4 专用路径更短、语义更少;强行把二进制协议塞进 HCM 只会制造伪 HTTP 与错误 codec。
flowchart TB
Chain["Selected filter chain"] --> TP["tcp_proxy"]
Chain --> HCM["http_connection_manager"]
TP --> L4["1:1 upstream connection"]
HCM --> Codec["HTTP/1 2 3 codec"]
Codec --> HF["HTTP filters"]
HF --> R["Router"]
谱系:可编程中间盒把「可插拔字节过滤器」做成组合点(Envoy network filter);HTTP/2 普及后,多数服务网格流量走 HCM,导致文档与示例偏 L7。开放问题仍在:非 HTTP 上游(Redis、Kafka、自有 RPC)应留在 L4/专用 network filter,还是用 tunnel / CONNECT 升到 HTTP——第 5、13 篇边界触及,本篇只固定分叉点。
失败对照(同端口、不同 chain):
| 配置终点 | 成功长什么样 | 失败常被误读成 |
|---|---|---|
tcp_proxy |
字节对拷、无 HTTP 状态码 | 「没有 200」其实从未进入 HCM |
http_connection_manager |
有 codec / stream / 路由统计 | 「TCP 通了」但 ALPN 错导致全协议错误 |
两行表的用途只有一个:写告警与 runbook 时先钉链终点类型,再选指标。
六、同链多 filter 时的顺序直觉
常见模式(示意):
- 可选:网络限流、RBAC(网络级)、Mongo 等协议 filter;
- 终端:
tcp_proxy或http_connection_manager(通常二选一作「消费连接」的终点)。
顺序错误的典型后果:在 HCM 之后再挂期望读原始字节的 filter——字节已被 codec 消费,行为未定义于「直觉上的透明串联」。配置审查先找谁终结字节流。
另一个常见误配:同一 chain 里既想「透传 TCP」又想「看 HTTP 头做路由」——做不到干净折中。正确做法是 FilterChainMatch 分成两条链(或上游协议专用 filter),而不是指望 network filter 列表线性「又透传又解码」。
七、参考资料
规范 / 官方文档(A)
- Envoy Proxy, Listener filters 中 Network (L3/L4) filters、TCP proxy filter,docs v1.39.0。
- Envoy Proxy, API v3
envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy;...http_connection_manager.v3.HttpConnectionManager。
源码(A)
envoyproxy/envoytag v1.39.0:source/common/network/、source/extensions/filters/network/。
站内对照
实验台账
- 无缓冲水位实测数字;不写跨代理延迟对比。
八、小结
- Network filter 吃字节与连接事件;HCM 是其中把世界升级到 HTTP 的那一个。
- 水印提供用户态反压;慢客户端 / 慢上游首先打满的是缓冲语义,不一定是路由表。
- TCP Proxy = 1:1 L4;HCM = 编解码 + HTTP filter + Router——选错坐标系会排错层。
- 下一篇专讲 HCM:codec、stream 与连接生命周期、编解码错误如何浮现。
→ 上一篇:Listener 与 FilterChainMatch · 系列目录 · 下一篇:HCM 与 Codec
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Envoy 数据面】HCM 与 Codec:HTTP/1·2·3、流生命周期与编解码错误
说明 HttpConnectionManager 如何把字节变成协议无关的 stream 事件;HTTP/1/2/3 codec 的职责边界;流与连接生命周期差异;以及 codec 错误与半关闭语义的失败模式。
【Envoy 数据面】HTTP filter 链与 Router:顺序语义、终端过滤器与重试超时落点
钉住 HCM 内 HTTP filter 的 decoder/encoder 顺序、Router 作为终端过滤器的含义,以及 retry/timeout 挂在路由与 Router 上的语义;说明「Router 之后再挂 filter」为何无效,并为 Cluster 上游选型铺路。
【Envoy 数据面】Main / Worker 与配置快照:事件循环、TLS 与几乎无锁热路径
钉住 Envoy 单进程多线程模型:Main 管 xDS/Admin,Worker 绑连接终生;Event::Dispatcher 与 Thread Local Storage 如何把配置变成每线程可读快照,以及快照解决什么、解决不了什么。
【Envoy 数据面】Listener 与 FilterChainMatch:accept、listener filters 与选链失败
拆解 Envoy accept 之后到 network filter 之前:listener filters 如何 enrich 元数据,FilterChainMatch 如何按 SNI/ALPN/目的地址选链,以及选错 chain 的典型失败模式。