上一篇把路径送到 accept 之后的 session。本篇钉住代理内核最容易混称的一组对象:connection、mux、stream 各管什么,TCP 与 HTTP 在哪一层分叉,超时究竟挂在谁身上。
把 HTTP/2
多路复用里的「一条流失败」当成「整连接死了」,或把
timeout connect 与 timeout 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.3 的
src/stream.c、src/mux_*.c、include/中 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/1(mux_h1)或
HTTP/2(mux_h2)等:
- 解析落在 mux;
- 解析结果进入 HTX(第 5 篇);
- stream 上挂 ACL、改写、选 backend、打上游。
同前端连接上: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。理解这层的实践价值是:
- 日志里的状态机/终止标志往往对应「卡在哪个分析阶段」,而不是笼统的「HAProxy 慢」。
- 某些选项(如强制关闭、不缓冲、管道化相关调谐)改变的是通道与分析器行为,表象却像超时配置写错。
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.c、include/haproxy/stream*.h |
stream 生命周期、超时字段、与前后端通道 |
src/mux_pt.c |
TCP / pass-through mux |
src/mux_h1.c、src/mux_h2.c(及同前缀) |
HTTP/1、HTTP/2 连接与流描述符、超时刷新 |
src/connection.c、xprt / ssl 相关 |
连接与传输层附着 |
src/stconn*.c、channel 相关 |
stream 与 mux 之间的连接器与缓冲通道 |
src/htx.c、src/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 篇)是同一演进线上的两步:先分离连接与流,再分离解析与处理。
争论:
- 统一 HTTP 语义 vs 保留胖 TCP 路径:统一便于观测与规则,但 L4 场景为 HTX/分析器付税是否值得——取决于是否真需要内容切换。
- 超时配置平面过于平坦:用户看到一串
timeout *,内核却是连接任务 + stream 字段 + mux idle 的叠加;文档必须靠「何时进入 tunnel」等状态来消歧,运维仍易拧错旋钮。
开放问题:
- H2/H3(若启用)下,stream 密集重置时,连接级 glitch 计数是否足够区分「客户端坏」与「上游池抖动」。
- Pass-through 与 HTTP 模式在同一进程混部时,线程 CPU 画像如何避免被「少数重 SSL HTTP」淹没(接 thread-group)。
- 独立半关闭 / FIN 超时在跨 mux 组合(H2 下游 + H1 上游)上的默认是否与平台 SRE 的心智模型一致——需第 5、13 篇的观测字段支撑。
工程间隙:规范描述帧与流;生产还要加队列、重试、健康检查。Mux/stream 只保证「事务对象存在」;会不会打到健康 server 是第 7–9 篇。
对排障读者的收束句:第 3 篇保证「听对口」,第 4 篇保证「认对对象」,第 5 篇保证「改对表示」——三者任一失败,第 6 篇的 ACL 再精巧也只是在错误层上空转。
八、参考资料
规范 / 官方文档(A)
- HAProxy 3.4.3, Configuration
Manual:
timeout client|server|connect|http-request|http-keep-alive|tunnel|queue等语义。 - HAProxy 3.4.3, Starter Guide / Management Guide:事件驱动与连接由单线程服务(与 stream 亲和一致)。
源码(A)
haproxy/haproxytag v3.4.3:src/stream.c、src/mux_pt.c、src/mux_h1.c、src/mux_h2.c及对应include/haproxy/头文件。
站内对照
实验台账
- 本篇无命令输出;超时行为以文档语义 + 源码路径推导,不编造触发日志。
九、小结
- Mux 解析线协议,stream 执行代理事务;混称会导致超时与日志读错层。
- TCP pass-through 与 HTTP mux 在解析税、超时集合、失败粒度上分叉。
timeout connect≠timeout client/server≠ keep-alive/tunnel——先问附着对象。- Channel / analyser / stconn
解释「卡在请求哪一阶段」;源码阅读从
stream+mux_*目录进。 - HTX 是 HTTP 路径上的下一层表示——解析与处理的分界在第 5 篇收紧。
→ 上一篇:Frontend / bind / accept · 系列目录 · 下一篇:HTX 与 HTTP 路径
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【HAProxy 数据面】HTX 与 HTTP 路径:内部表示、改写落点与协议分叉
说明 HTX 作为版本无关的内部 HTTP 表示如何分离解析与处理;http-request 改写/重定向落在 HTX 而非永恒原始缓冲;HTTP/1 与 HTTP/2 在 mux 边界的分叉;以及相对纯 L4 路径的成本模型——非应用层菜谱。
【HAProxy 数据面】数据面全景:从 Frontend 到 Seamless Reload 的代理内核
定位 HAProxy 相对 network/56 配置运维、Nginx 多进程与 Envoy Filter/xDS 的生态位;钉住请求路径、线程共享、协议表示、动态运维、失败归因五条坐标系,并给出 14 篇阅读路线。
【HAProxy 数据面】线程与调度:nbthread、Thread-group 与共享内存边界
钉住 HAProxy 单进程多线程模型:每线程事件循环、连接单线程服务;对照 nbproc 下 stick-table 不共享,以及 Nginx 多进程与 Envoy Main/Worker;说明 poller 线程上阻塞的软警告。
【HAProxy 数据面】Frontend / bind / accept:监听、FD 与接入路径
说明 HAProxy frontend 与 bind 如何落到监听套接字;accept 之后 FD 与线程的关系;SO_REUSEPORT / cpu-map 与多线程接入;以及 expose-fd listeners 作为 seamless reload 的前置,并点到 content switching 入口而不做 ACL 百科。