土法炼钢 · 系统与基础设施

【HAProxy 数据面】Stream 与 Mux:连接之上的协议分层

文章导航

分类入口
networkproxy
标签入口
#haproxy#stream#mux#multiplexer#tcp#http#timeout#3.4

目录

上一篇把路径送到 accept 之后的 session。本篇钉住代理内核最容易混称的一组对象:connection、mux、stream 各管什么,TCP 与 HTTP 在哪一层分叉,超时究竟挂在谁身上。

把 HTTP/2 多路复用里的「一条流失败」当成「整连接死了」,或把 timeout connecttimeout client 当成同一把刀,都会在日志里选错字段、在配置里拧错旋钮。

本文说明:对象分层与数据流;TCP mux 与 HTTP mux 的职责差;超时附着点;失败归因表;以及 v3.4.3 源码树里该打开哪些目录。

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

篇目 核心内容
第 3 篇 · Frontend Accept 听到连接
第 4 篇 · Stream Mux 分层、超时、TCP vs HTTP
第 5 篇 · HTX HTTP 内部表示与改写落点
第 7 篇 · Backend LB 上游侧连接与队列

版本锚定:HAProxy 3.4.3 Configuration Manual(timeout 关键字语义)、Starter Guide(代理模式);源码 tag v3.4.3src/stream.csrc/mux_*.cinclude/ 中 stream/mux 相关头文件。不伪造 show sess 输出。


一、对象分层:connection → mux → stream

工作定义(与官方文档用语对齐,便于和源码目录对读):

对象 是什么 典型销毁/结束条件
Connection 传输会话:下游或上游的一条套接字(可经 SSL xprt) 对端关闭、错误、空闲超时、进程 soft-stop 排空
Mux(multiplexer) 连接上的协议状态机:如何把字节变成「零条或多条」逻辑流 随连接拆除;HTTP/2 等可在连接仍在时结束单流
Stream 代理内部一次「请求/会话」工作单元:分析器、通道、前后端 stconn、超时字段 事务结束、错误、超时;HTTP keep-alive 下同连接可有后续 stream
Session 客户端侧会话上下文(前端连接相关的持久信息) 前端连接生命周期相关
flowchart TB
  subgraph down ["Downstream"]
    DConn["connection"]
    DMux["mux_h1 / mux_h2 / mux_pt / ..."]
    DConn --> DMux
  end
  subgraph core ["Proxy core"]
    Str["stream"]
    ChReq["channel req"]
    ChRes["channel res"]
    Str --> ChReq
    Str --> ChRes
  end
  subgraph up ["Upstream"]
    UMux["mux_*"]
    UConn["connection"]
    UMux --> UConn
  end
  DMux --> Str
  ChReq --> UMux

直觉:mux 懂线协议;stream 懂「代理这一跳要干什么」。 HTX 是 HTTP 路径上 stream/mux 之间传递的消息形态(第 5 篇),TCP 路径可以几乎不出现 HTX。

Envoy HCM/codec 对照:Envoy 用 codec 把 H1/H2/H3 抬到协议无关 stream 事件;HAProxy 用 mux_* 家族做同类边界。两边都要求运维区分 connection 级与 stream 级故障。

若只能记一张图:下游 connection 上挂一个 mux,mux 向核心贡献零到多条 stream;每条 stream 再经通道连到上游 mux/connection。排障时任何「连接还在」的表述,都必须追问是哪一侧连接、以及该连接上的哪一条 stream


二、TCP 路径 vs HTTP 路径

2.1 TCP / pass-through

mode tcp(及隧道类场景)倾向使用 pass-through mux(源码侧常见入口文件名 mux_pt.c):少解析应用语义,侧重字节转发;在条件允许时可走更快的转发路径(实现上与 splice / zero-copy 调谐相关,细节以 tag 内 flag 为准,本篇不写未测加速比)。

超时与关闭语义更接近「两条方向的字节泵 + 连接超时」,而不是「请求头到了才开始事务时钟」。

2.2 HTTP 路径

mode http 下,下游 mux 通常是 HTTP/1mux_h1)或 HTTP/2mux_h2)等:

同前端连接上:H1 常表现为串行事务(keep-alive 复用连接);H2 表现为多 stream 并发。因此「一条 H2 stream 被 reset」与「H1 连接直接掐断」不是同一症状——日志与计数必须能区分。

WebSocket / CONNECT 等升级路径仍先经过「能说话的 mux + stream」,不是绕过 frontend 的旁路魔法;本系列不展开应用菜谱。

2.3 上下游协议可以不一致

下游 H2、上游 H1(或反之)是常态:stream 在核心把一侧 mux 的消息形态转到另一侧。连接池与复用策略挂在 backend/server 与上游 mux 上(第 7 篇),不要假设「下游是 H2 就自动上游也复用同一套帧」。


三、超时挂在哪一层

配置里的 timeout 名称容易让人以为「都是一个 timer」。机制上至少要分清:

配置名(常见) 概念附着点 误用时的假象
timeout client 前端侧等待/空闲(经 stream/前端 stconn 与 mux 空闲逻辑协作) 上游慢却一直拧 client
timeout server 后端侧等待 客户端慢却一直拧 server
timeout connect 与服务器建立连接的时限 已连接上的读写慢——不归它管
timeout http-request 收齐 HTTP 请求(头/请求行阶段) TCP 模式无效或语义不同
timeout http-keep-alive 事务间隙的 keep-alive 空闲 与「事务进行中」的 client/server 超时混淆
timeout tunnel 进入隧道/升级后的双向转发 仍按 HTTP 请求超时去调 WebSocket
timeout queue 在后端队列里等待 server 名额 当成 connect 失败

源码侧(v3.4.3 概念级):stream 上可见 connect_timeout / queue_timeout / tunnel_timeout 等字段,以及前后端 stconn 的 I/O 超时;H1 mux 自身还维护 connection 级 task 的 idle/timeout/shut_timeout(例如 keep-alive 与 half-close 阶段)。排障时要问:到期的是连接空闲任务,还是 stream 上的 connect/queue,还是通道 I/O?

规则引擎可用 set-timeout 类动作覆盖部分值(存在则不应被后端默认值无条件盖掉——实现以 stream_set_timeout 一类逻辑为准)。本篇不列动作百科。

另一个常见混淆是 client-fin / server-fin(半关闭后的收尾超时)与正常读写超时:连接已经半关,mux 可能切换到 shut_timeout 一类逻辑(H1 mux 的超时刷新路径即此)。若在 FIN 已发出后仍用「事务中」的 server 超时去解释,会得到错误的根因故事。

排障时建议把超时问题写成三元组:(对象=connection|stream|queue, 阶段=handshake|headers|body|idle|tunnel, 角色=client|server|connect)。配得越具体,越不容易在配置文件里来回拧同一把无关的刀。


四、失败归因:先分层,再查池

症状 优先怀疑 说明
握手后立即断开、无 HTTP 日志 接入 / TLS bind / mux 选错 回到第 3、10 篇
有请求行/头错误、连接仍在 H1/H2 mux 解析或协议违规 流级或连接级 glitch
单请求 503,同连接后续还活 stream / 上游 connect / 队列 / 健康检查 连接 ≠ 所有 stream 健康
timeout connect 触发 上游建连 看 server 可达性与 timeout connect,不是 client 空闲
keep-alive 间隙断开 http-keep-alive / 前端空闲 与事务中 server 超时分开
H2 单 stream reset 流级错误或上游 reset 不要整连接重绑亲和

与第 1 篇请求路径轴对齐:本篇覆盖 mux ↔︎ stream;选错 backend 属规则轴(第 6 篇);池子与 outlier 属上游轴(第 7、9 篇)。


五、通道与分析器:stream 内部还有一层

Stream 不是「一个请求结构体」就结束。两侧各有 channel(请求方向 / 响应方向),分析器(analyser)按阶段挂在通道上:例如等待请求头、执行 HTTP 规则、准备连接上游、转发 body。理解这层的实践价值是:

Stream connector(stconn) 把 stream 接到前后端 mux:上游尚未建连时,后端侧连接器可以处于等待;timeout connect 与队列等待在这一阶段最容易被误读成 server 读写超时。第 7 篇讲 LB 与连接复用时会回到同一接缝。

过滤器 / Lua 等扩展若注册在 stream 生命周期上,其执行上下文仍是持有该连接的线程。扩展里的阻塞与第 2 篇警告是同一类故障,只是症状更常被说成「某个 ACL 很慢」。


六、源码地图(v3.4.3,目录级)

haproxy/haproxy tag v3.4.3 上建议按这条阅读路径前进(先目录、再符号):

路径 看什么
src/stream.cinclude/haproxy/stream*.h stream 生命周期、超时字段、与前后端通道
src/mux_pt.c TCP / pass-through mux
src/mux_h1.csrc/mux_h2.c(及同前缀) HTTP/1、HTTP/2 连接与流描述符、超时刷新
src/connection.c、xprt / ssl 相关 连接与传输层附着
src/stconn*.c、channel 相关 stream 与 mux 之间的连接器与缓冲通道
src/htx.csrc/http_htx.c 下一篇:HTX

不在正文粘贴大段源码;核对行为时以该 tag 中函数实现为准。版本漂移时先看 mux 文件头注释与 MX_FL_* 一类能力旗标。

阅读策略建议:先选一个最小复现(纯 TCP listen,或单 H1 frontend),用 tracing / 日志把「新连接 → 新 stream → 上游 connect」的次序钉住,再打开对应 mux_*.c 的 init/attach 路径。一上来同时读 H2 窗口与 stick-table,很容易把三层故障揉成一团。

与第 2 篇的衔接再次强调:上述所有对象默认生活在同一工作线程的地址空间与事件环上;跨线程迁移连接不是 HAProxy 的常规操作。因此「某个 stream 的分析器死循环」等于「该线程上其他连接一起饿」——失败归因必须带线程维度。


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

谱系:早期用户态代理常把「连接 = 会话 = 请求」揉在同一结构里;HTTP/2 迫使 多路复用层独立出来。HAProxy 的 mux 抽象与后来 HTX(第 5 篇)是同一演进线上的两步:先分离连接与流,再分离解析与处理。

争论

开放问题

  1. H2/H3(若启用)下,stream 密集重置时,连接级 glitch 计数是否足够区分「客户端坏」与「上游池抖动」。
  2. Pass-through 与 HTTP 模式在同一进程混部时,线程 CPU 画像如何避免被「少数重 SSL HTTP」淹没(接 thread-group)。
  3. 独立半关闭 / FIN 超时在跨 mux 组合(H2 下游 + H1 上游)上的默认是否与平台 SRE 的心智模型一致——需第 5、13 篇的观测字段支撑。

工程间隙:规范描述帧与流;生产还要加队列、重试、健康检查。Mux/stream 只保证「事务对象存在」;会不会打到健康 server 是第 7–9 篇。

对排障读者的收束句:第 3 篇保证「听对口」,第 4 篇保证「认对对象」,第 5 篇保证「改对表示」——三者任一失败,第 6 篇的 ACL 再精巧也只是在错误层上空转。


八、参考资料

规范 / 官方文档(A)

源码(A)

站内对照

实验台账


九、小结

  1. Mux 解析线协议,stream 执行代理事务;混称会导致超时与日志读错层。
  2. TCP pass-through 与 HTTP mux 在解析税、超时集合、失败粒度上分叉。
  3. timeout connecttimeout client/server ≠ keep-alive/tunnel——先问附着对象。
  4. Channel / analyser / stconn 解释「卡在请求哪一阶段」;源码阅读从 stream + mux_* 目录进。
  5. HTX 是 HTTP 路径上的下一层表示——解析与处理的分界在第 5 篇收紧。

上一篇:Frontend / bind / accept · 系列目录 · 下一篇:HTX 与 HTTP 路径

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。


By .