上一篇钉死了线程与共享内存边界。本篇进入请求路径轴的第一段:连接如何被
frontend 的 bind 接住,accept 之后 FD 落在谁手里,以及
reload 为何要提前谈
expose-fd listeners。
常见误区:把 frontend
当成「只是配置分组名」,或把 accept 失败一律归因于
backend。实际上大量「连不上 / 偶发 RST」落在监听与
FD 交接窗口,此时上游池再健康也救不了。
本文说明:frontend / bind / listener 的职责切分;accept
路径与 FD 模型;SO_REUSEPORT 与
cpu-map
对多线程接入的意义;expose-fd 作为第 12
篇的前置;以及 content switching 的入口位置——不做 ACL
百科。
本文是「HAProxy / 数据面代理内核」系列第 3 篇(共 14 篇)。→ 系列目录
篇目 核心内容 第 2 篇 · 线程与调度 谁跑事件循环 第 3 篇 · Frontend Accept bind、accept、FD、reuseport 第 4 篇 · Stream Mux 连接之上的协议分层 第 12 篇 · Seamless Reload FD 交接与排空全文
版本锚定:HAProxy 3.4.3 Configuration Manual(
frontend/bind/expose-fd)、Management Guide(reload、-x、SO_REUSEPORT);源码 tag v3.4.3。无伪造 accept 计数或show sess输出。
一、Frontend 与 bind:谁负责「听」
配置模型(与 network/56 一致,本篇只收机制):
| 对象 | 职责 | 本篇关注点 |
|---|---|---|
| frontend | 面向客户端的代理入口:模式(tcp/http)、超时、ACL/规则挂载、默认 backend | 规则在「已有连接与协议视图」之后求值 |
| bind | 在 frontend(或 listen)上声明监听地址/端口/协议选项(SSL、接口、线程限制等) | 真正创建 listening FD 的配置面 |
| listen | frontend+backend 合体快捷段 | 接入语义与 frontend 相同,不另造第三套 accept |
一个 frontend 可有多条
bind:多端口、多证书、IPv4/IPv6、或绑到不同线程集。失败归因时先问:哪一条
bind 在听?SSL 是否在 bind 上终结? 而不是先翻
backend 服务器列表。
flowchart TD
Bind["bind line(s)"] --> LFD["Listening FD(s)"]
LFD --> Accept["accept on a worker thread"]
Accept --> Sess["session"]
Sess --> Mux["mux attach"]
Mux --> Stream["stream"]
Stream --> Rules["ACL / content switching"]
Rules --> Be["backend selection"]
二、Accept 路径:从 SYN 到 session
概念顺序(机制层,不绑定未核对的函数名):
- 内核完成 TCP(或 TLS 终结前的 TCP)握手,连接进入 listening socket 的 accept 队列。
- 某工作线程的 poller 发现监听 FD 可读,执行
accept(及后续非阻塞设置、可选accept-proxy等)。 - 新建 session(客户端侧会话上下文),再按 frontend 模式挂上合适的 mux,并 Instantiation stream(第 4 篇)。
- 若
mode http,后续才进入 HTX 与 ACL(第 5–6 篇);mode tcp则尽早走透传向 backend。
与第 2
篇衔接:接受该连接的线程通常就是服务该连接终生的线程。多线程下「谁
accept 到」由内核与监听套接字选项共同决定——这把我们引到
SO_REUSEPORT。
排队与过载:maxconn 等限制在
frontend/全局生效时,表现是拒绝或延迟新连接,不是
backend 502。把「入口满」与「上游满」分开,是第 13
篇排障清单的前置。
SSL 在 bind
上终结时,握手消耗发生在接入线程上:这会把「TLS
CPU」与「HTTP 规则 CPU」叠在同一条连接亲和链里。若某条 VIP
证书套件特别重,只加 backend 机器无效——应回看线程组与
cpu-map(第 2、10 篇),而不是先怀疑 HTX。
会话建立失败(内存池耗尽、FD 耗尽)同样属于接入/资源轴:症状常是短连接风暴下的系统性拒绝。Management Guide 强调进程尽量少做文件系统访问、依赖池化与严格内存上限——这些约束解释了为何「机器还有空闲内存却开始拒连」仍可能发生:代理有意的上限,不是内核 OOM 的同义词。
三、FD 模型:听套接字 vs 已连接套接字
| FD 角色 | 生命周期 | Reload 敏感点 |
|---|---|---|
| Listening FD | 随 bind 创建;进程退出或 unbind 时关闭 | 新旧进程如何交接,决定 SYN 是否掉进空洞 |
| Accepted FD | 随连接;由单线程事件循环持有 | soft-stop 时旧进程继续服务直至排空 |
| Stats / admin socket | 运维面 | 无 master-worker 时,-x
取听套接字依赖其能力 |
Management Guide 对 graceful
stop(SIGUSR1)的定义是:停止监听,但继续处理已建立连接,直到最后一个连接结束。Hard
stop(SIGTERM)则立即退出并关掉已建立连接。Reload
脚本通常让新进程起来后再对旧进程发 finish——细节第 12
篇。
chroot
与「运行中不能原地重读配置文件」同一哲学:变更配置 =
新进程,不是在旧地址空间里热补丁整棵代理树。Runtime
API 只能覆盖子集(第 11 篇)。
四、SO_REUSEPORT
与 cpu-map:多线程接入的部署旋钮
4.1 SO_REUSEPORT
Management Guide 在 reload 竞态段写得很清楚:在支持
SO_REUSEPORT
的内核上,新进程可以在旧进程仍监听时 bind
同地址,从而缩小「谁都不听」的窗口;Linux
需足够新的内核(文档点名 3.9+ 一带的历史)。命令行
-dR 可禁用监听上的
SO_REUSEPORT(等价于配置侧关闭)——这是调试/对照开关,不是性能银弹。
多进程/nbproc
时代,文档还强调:每进程用独立 listening
socket 绑同一
IP:port,内核才能较均匀地唤醒进程,而不是惊群式叫醒所有人。多线程模型下,reuseport
与线程调度器共同影响「哪条线程先拿到连接」;它不把连接变成可迁移对象。
4.2 cpu-map
/ 线程限制在 bind 上
cpu-map 与 bind
级线程亲和相关选项,用于把特定监听流量收束到部分线程或 CPU
集合(精确关键字以 3.4 Configuration Manual
为准)。典型动机:
- SSL 重的 frontend 与轻量健康检查/stats 分流到不同核;
- 与网卡多队列中断的 L3 共享域对齐(接第 2 篇中断建议)。
这是部署与局部性工具。配错时的症状是:某几核打满、其余空闲,或 reload 后亲和失效——应查 cpuset / 配置是否被容器收窄,而不是先怀疑 mux。
五、expose-fd listeners:seamless
reload 的前置
无 master-worker 时,新进程可通过
-x <unix_socket> 连到旧进程,取回
listening FD
并继续用,避免「先停听再绑」的空洞(Management
Guide:-x 节)。前提是 stats socket 上启用
expose-fd listeners。
Master-worker 模式下,master 可在 reload
时自动走类似路径(文档描述可用 sockpair@
等机制直接连 worker),不一定再要求配置里写
expose-fd listeners;若要禁用自动
-x 行为,文档给出传 -x /dev/null
的做法。
本篇只钉前置条件与意图:
| 模式 | 取回 listening FD 的典型条件 |
|---|---|
| 经典双进程 reload | stats socket + expose-fd listeners + 新进程
-x |
| master-worker | master 协调;可弱化对配置项的依赖 |
完整窗口、丢包形态、与 Nginx reload / Envoy hot-restart 对照 → 第 12 篇。 这里只强调:谈 seamless 却没弄清 FD 从哪来,等于只做了「起新进程」。
一个实用检查顺序(仍不粘贴命令输出):先确认当前是
master-worker 还是古典双进程脚本;再确认旧进程 stats socket
是否具备交出 listening FD 的能力;最后才看新进程是否带着
-x 启动。三步任一缺失,都可能退化成「靠
SO_REUSEPORT 硬绑」或「先 pause 旧监听再
bind」的窗口更大路径——Management Guide
已说明后者在高压下更易被察觉。
六、Content switching 入口(非 ACL 百科)
Accept 与 mux 建立之后,HTTP 模式下规则引擎才看到方法、路径、首部等(经 HTX,第 5 篇)。Content switching 在机制上的位置是:
stream 已存在 → 分析器 / ACL 求值 → 选择 backend(或 reject / redirect)
本篇不列 ACL 匹配器清单——那是 network/56 与第 6 篇的职责。这里只需记住失败模式:
| 症状 | 更可能落点 |
|---|---|
| TCP 都连不上 | bind / maxconn / 防火墙 / reload 窗口 |
| 连上后协议错乱 | 模式(tcp/http)、bind SSL、mux 选择 |
| 连上且协议对,但进错池 | ACL / use_backend(第 6–7 篇) |
| 进对池仍 503 | server / 健康检查 / 队列(第 7、9 篇) |
TCP 模式下「content switching」能力更窄,但仍可能基于层四信息或检查结果做后端选择;不要假设「mode tcp = 没有规则」。只是规则看不见 HTTP 字段,失败模式也更少表现为 4xx 文本,而更多表现为连接重置或静默选错池。
与 Envoy Listener / FilterChainMatch 对照:Envoy 在「选链」阶段就可能因 SNI/ALPN 走错世界;HAProxy 的 bind 已相对静态地决定「这个口是否 SSL、是否 HTTP」,更细的切换推到 stream 规则。两边都要求:先固定接入层,再查路由层。
七、Accept 队列、backlog 与「连得上但奇慢」
内核 listen backlog、HAProxy 全局/frontend 连接上限、以及线程是否及时把监听 FD 抽空,共同决定接入是否成为瓶颈。机制层要区分三类现象:
| 现象 | 可能机制 | 不要先做的事 |
|---|---|---|
| SYN 重传 / 连接超时 | 没人听、backlog 满、防火墙丢 SYN、reload 空洞 | 调 timeout server |
| 建连成功但首字节极慢 | 线程阻塞、SSL 握手排队、accept 后会话初始化重 | 只加 backend 机器 |
| 间歇性拒绝 | maxconn、全局限流、某条 bind
的线程子集打满 |
以为是 DNS |
多队列网卡 + reuseport 时,还可能出现「只有部分 RSS 队列对应的核在接新连接」的假不均——这与第 2 篇中断绑定建议是同一张图的两面。验证手段属于观测篇:看每线程会话/连接计数是否长期倾斜,而不是只看整机 CPU 平均值。
PROXY protocol / accept-proxy
类选项把「真实客户端地址」从外层负载均衡器传入:它们挂在
accept
之后、规则看见地址之前。配错时的典型症状是
stick-table 与 ACL
基于错误源地址做决策——问题仍在接入元数据层,不是 LB
算法坏了。
八、谱系、争论与开放问题
谱系:监听套接字 + 事件驱动 accept 是用户态代理的共同祖先;HAProxy 把「多 bind、多模式、可 FD 交接」做成运维可叙事的一等路径,而不是隐藏在发行版 init 脚本里的魔法。
争论:
- Reuseport 均分 vs 显式线程绑定:前者运维简单,后者在 SSL/NUMA 不均时更可控;错误绑核会造成自我拒绝吞吐。
- Master-worker 自动 FD 交接 vs 显式
expose-fd:自动化减少漏配,却让「谁持有听套接字」更难用古典lsof心智推理——第 12 篇必须给可观测抓手。
开放问题:
- 容器 cgroup / 多网卡队列下,reuseport 哈希是否与
cpu-map预期一致,如何从 stats 侧验证。 - 极多 frontend/bind 时,reload 的 pause/bind 序列时长是否成为主窗口(Management Guide 已定性毫秒级窗口与负载相关失败率量级——引用文档定性,不作本机外推)。
- Accept 限速与 frontend
maxconn在多线程下的公平性:是否出现「部分线程饿死入口」。
九、参考资料
规范 / 官方文档(A)
- HAProxy 3.4.3, Configuration
Manual:
frontend、bind、expose-fd listeners、与 bind 相关的 SSL/线程选项。 - HAProxy 3.4.3, Management
Guide:graceful/hard stop;reload
两窗口;
SO_REUSEPORT;-x与 master-worker 行为;-dR。
源码(A)
haproxy/haproxytag v3.4.3:listener / frontend / connection accept 相关src/(目录级;函数名以 tag 为准)。
站内对照
实验台账
- 本篇无命令输出;不写 accept QPS。
十、小结
bind创造 listening FD;frontend 挂规则与模式;accept 之后连接钉在工作线程上。SO_REUSEPORT/cpu-map影响谁接到连接与 CPU 局部性,不改变「连接不迁移」。expose-fd listeners(或 master-worker 等价物) 是 seamless reload 取回听套接字的前置——细节见第 12 篇。- Content switching 在 stream 规则层;接入失败不要先查 ACL 百科。
- Backlog / maxconn / PROXY 元数据 与「上游 503」分属不同轴;分层检查表从第 1 篇带来的顺序在此仍然适用。
→ 上一篇:线程与调度 · 系列目录 · 下一篇:Stream 与 Mux
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【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 数据面】Stream 与 Mux:连接之上的协议分层
厘清 connection → session → stream 与 mux 的分层;对比 TCP pass-through 与 HTTP mux;说明超时挂在哪一层对象上,以及失败归因如何区分连接级与流级;源码目录级指向 stream 与 mux_*。
【HAProxy 数据面】HTX 与 HTTP 路径:内部表示、改写落点与协议分叉
说明 HTX 作为版本无关的内部 HTTP 表示如何分离解析与处理;http-request 改写/重定向落在 HTX 而非永恒原始缓冲;HTTP/1 与 HTTP/2 在 mux 边界的分叉;以及相对纯 L4 路径的成本模型——非应用层菜谱。