第 11 篇 把「能热改」与「必须 reload」切开。本文回答下一层:reload 时 listen FD 如何交接、旧进程如何 soft-stop、stick-table 与 peers 保住什么、相对 Nginx reload / Envoy hot-restart 差在哪。
依据 HAProxy 3.4.3 Management
Guide 的信号模型、-x /
expose-fd listeners / master-worker
sockpair@ 说明。不粘贴伪造 reload
日志;不写未测延迟排名。
本文是「HAProxy / 数据面代理内核」系列第 12 篇(共 14 篇)。→ 系列目录
篇目 核心内容 第 11 篇 · Runtime API 热改 vs 结构 第 12 篇 · Seamless reload FD 交接、soft-stop、状态 第 13 篇 · 排障与可观测 五轴清单
版本锚定:Management Guide 3.4.3 — graceful/hard stop、reload 窗口、
-x、master-worker 对SIGUSR2的响应。对照 Envoy Hot restart / drain、Nginx 深度。
一、问题:结构变更如何不停听
结构变更必须由新进程镜像解析配置。若新进程自行
bind
而旧进程仍占端口,会撞车;若先停旧再起新,会空窗。Seamless
reload 的目标是:新进程拿到(或共享)listen
能力,旧进程停止 accept 但仍排空已有连接。
文档用语:
| 信号 / 选项 | 语义 |
|---|---|
SIGUSR1(-sf finish) |
Graceful / soft-stop:解绑监听,处理完存量连接后退出 |
SIGTERM(-st terminate) |
Hard stop:立即退出,切断连接 |
SIGUSR2(master) |
master-worker 下由 master 触发 reload 流程 |
-x <unix_socket> |
新进程从旧进程取回 listening sockets,避免漏接新连接(Linux) |
「不丢连接」在这里的精确含义是:存量连接留在旧进程排空;不是把
TCP 会话 splice 到新进程。长活连接要么在 soft-stop
窗口内结束,要么被 hard-stop-after
等策略切断——与 Envoy「连接不迁移」同族。
把 seamless reload
理解成「零失败数学保证」会过度承诺。Management
Guide
明确写了高新建连接速率下仍可能出现少量失败,并区分两类窗口:pause/resume
竞态,以及 listen 关闭瞬间 backlog/SYN 变
RST。工程目标应是:选择正确交接路径(-x
/ sockpair)+ 足够新的内核 SO_REUSEPORT 支持 +
控制 reload
频率,把失败压进噪声;而不是在变更流水线里宣称「绝对无感」。
与第 11 篇合读:能 Runtime 解决的不要 reload——每次结构 reload 都引入双进程与状态迁移问题。反过来,为了「躲 reload」把本该进 Git 的结构变更长期堆在内存里,会在下次被迫 reload 时一次性爆掉。健康节奏是低频结构 reload + 高频受控 Runtime。
二、FD 交接:三种路径
sequenceDiagram
participant Old as OldWorker
participant New as NewWorker
participant M as Master
Note over M,New: master-worker path
M->>New: Fork after cfg parse
New->>Old: Retrieve listen FDs via sockpair
New->>New: Accept new conns
M->>Old: SIGUSR1 soft-stop
Note over Old: Drain existing streams
2.1 无
master-worker:-x +
expose-fd listeners
非 master-worker 时,新进程用 -x
连接旧进程的 stats socket,取回 listening
FD。能力必须在配置里打开:
global
stats socket /run/haproxy/admin.sock mode 600 level admin expose-fd listenersexpose-fd listeners 让该 socket
允许把 listener FD
交给对端;没有它,-x 取 FD
的路径不可用。典型启动形态(文档示例语义):
haproxy -f /etc/haproxy/haproxy.cfg \
-D -p /var/run/haproxy.pid \
-sf $(cat /var/run/haproxy.pid) \
-x /run/haproxy/admin.sock-sf 在新进程就绪后向旧 PID 发
finish;-x 负责 FD 交接。init/systemd
单元通常封装上述步骤。
2.2
master-worker:自动 sockpair@,可不配
expose-fd
Management Guide 写明:在
master-worker 下,reload
不需要
expose-fd listeners。Master 在 reload 时自动用
sockpair@ 语法直连 worker,取回 listening
sockets,不必经过配置里声明的 stats
socket。若要禁用该行为,可传 -x /dev/null。
Master 收到 SIGUSR2 后:带着
-sf 与旧 worker PID 列表 re-exec /
解析配置、fork 新 worker,再完成交接与
soft-stop。运维面常见入口是
systemctl reload haproxy(具体 unit
以实现为准)。
Master CLI(-S)不能用于取
FD——文档单独警告:该 socket 不可 retrieve listening
sockets。
systemd / 发行版 unit 通常把「校验配置 → 发信号 / 带
-sf 拉起」封进 reload
动作。机制文关心的是单元背后的语义是否等价于「新进程取得
listen FD 且旧进程 soft-stop」;若自定义包装脚本丢掉了
-x 又未启用 master-worker 自动
sockpair,就会默默退回 pause 竞态路径。升级 HAProxy
大版本或迁移容器入口时,应重新核对 unit
与文档选项,而不是假设「reload 一词永远同义」。
容器场景额外注意:listen FD 交接依赖旧进程仍可达且 socket 路径一致;若编排层先杀旧容器再起新容器,那是滚动重建,不是 seamless reload。二者都合法,但失败模式不同——前者可能有短暂空窗,后者依赖同机进程协作。
2.3 无 -x
时的 pause/resume 竞态
若新进程无法直接继承 FD,而走「先让旧进程
pause(SIGTTOU)再 bind、失败则
SIGTTIN
resume」路径,文档记载存在毫秒级窗口:端口既不在旧进程听、也尚未被新进程绑上。高新建连接速率下可能出现少量失败;SO_REUSEPORT(现代
Linux)可显著缩小该类竞态。另有 backlog/SYN 在旧 listen
关闭瞬间变 RST
的系统相关边角。结论:生产应优先走
-x / master-worker 自动交接,而不是依赖 pause
窗口碰运气。
历史包袱:早期环境若内核过旧、缺少可靠
SO_REUSEPORT,运维有时用防火墙在 reload
前后短暂拦 SYN,逼客户端重传——这是系统相关权宜之计,不是
HAProxy
默认承诺。新部署应先验证交接路径与内核能力,而不是默认上防火墙补丁。WSL/容器网络命名空间若把
listen
行为变得与裸机不同,应用本机实验确认,不要直接抄发行版 unit
假设。
把三种路径写成检查表:
- 是否
master-worker?是 → 确认未传-x /dev/null关掉自动交接。 - 否 → stats socket 是否带
expose-fd listeners,reload 是否带-x? - 任一「否」→ 变更窗口按「可能有竞态」管理:降频、错峰、观察新建失败率。
三、旧进程排空:soft-stop 时间线
1. 新进程解析配置成功并取得 listen 能力
2. 旧进程收到 SIGUSR1:停止(或已停止)accept 新连接
3. 旧进程继续服务已建立 stream,直到自然结束
4. 连接耗尽后旧进程退出;内存池在 graceful 路径上会尽量释放给新进程
运维含义:
- Reload ≠ Restart:restart/hard stop 会切连接;reload 走 soft-stop。
- 超长 WebSocket / 流式会话会拉长双进程共存窗口;需用
hard-stop-after等(Configuration Manual)设上限,否则 leaving worker 长期占 FD/内存。 - Runtime 热改只存在于当时那个进程内存;旧进程上的 drain 状态不会「自动合并」进新进程——新进程按文件 + 可选 state/peers 起步。
Master CLI 的 show proc 在窗口内应能看到
leaving worker;排障时对 @!旧PID
做只读查询,避免误判「reload 已完全切到新语义」。
排空期间可观测性容易骗人:stats 页面或默认 socket
可能主要反映 current worker,而延迟敏感的长连接仍在 leaving
上。第 13 篇把「Reload /
Runtime」单列一轴,原因正在于此。发布系统若只探针「新进程
show info 成功」就标绿,可能在 leaving
仍持有大量会话时过早继续下一波变更。
hard-stop-after(及同类超时)是安全阀:没有它,异常客户端可以无限拖住旧进程,导致
FD、内存与线程组长期双倍占用。设多少秒必须看连接时长分布——边缘网关与内部
LB 的答案不同;本篇不写未测「推荐秒数」。
四、Stick-table / peers:reload 保留什么
| 状态 | 默认 reload 行为 | 保留路径 |
|---|---|---|
| Stick-table 条目 | 清空(新进程空表) | peers 段 + 表声明
peers <section>;单机也可把本机列为
peer,用于跨 reload 同步 |
| Runtime 改的权重/state | 丢失(除非写入 state 并加载) | show servers state 落盘 + 启动加载;或回写
cfg |
| Stats 计数器 | 新进程从零(或 stats-file 预载) | 文档中的 stats-file / preload 机制 |
| 已建立 TCP/HTTP 会话 | 留在旧进程排空 | 无迁移;客户端可能在排空后重建到新进程 |
Peers 协议本为多机 stickiness / 限流共享设计;「单机 peers 抗 reload 丢表」是同一机制的副作用(维护者博客有操作说明,属 B 级辅证;配置语法以 Configuration Manual 为准)。未配 peers 时,限流桶与粘滞在 reload 瞬间归零——这是事故常见根因,不是「seamless」承诺的一部分。
机制细节与容量失败模式见 第 8 篇。工程上建议把「该表是否 peers 保护」写成配置评审项:凡用于安全封禁或计费粘滞的表,默认要求 peers;仅用于短窗调试计数的表,可接受 reload 清空并在变更说明里写明。
State file 路径与 stick-table/peers 正交:前者主要服务 server 运行态在 reload 后的复现;后者服务表条目同步。只做其中一个,仍可能看到「机器还在、限流桶没了」或「桶还在、权重回到文件值」——排障时按第 13 篇分轴,不要混成一个「状态丢了」黑盒。
五、对照:Nginx reload 与 Envoy hot-restart
| 维度 | HAProxy seamless reload | Nginx reload | Envoy hot-restart |
|---|---|---|---|
| 触发 | 配置结构变更;master SIGUSR2 或
-sf 新进程 |
配置变更;master 拉新 worker | 二进制/不可热替换状态;epoch |
| Listen 交接 | -x / expose-fd / sockpair |
新 worker bind;旧 worker 退出 | UDS RPC 传 listen socket |
| 存量连接 | 旧进程 soft-stop 排空 | 旧 worker 排空 | 父进程 drain;不迁移 |
| 统计/状态 | peers / state file 可选 | 共享区因产品而异 | 共享内存对齐 counter/gauge |
| 动态配置主路径 | Runtime API(第 11 篇)缩小 reload 面 | 多数变更仍 reload | xDS 优先;hot-restart 是例外 |
与 Envoy 第 12 篇 对齐的判决句:三者都是「新听旧排」,都不是连接热迁移。 HAProxy 的差异在于:日常运维大量变更可先吃 Runtime,把 reload 留给结构与版本;Envoy 则把日常留给 xDS,把进程级留给 hot-restart。Nginx 侧见 network/55;产品层横向表见 network/60;配置菜谱见 network/56。
文档对高新建连接下的少量失败有数量级描述(约每 \(10^{4}\)
新建/秒量级一次失败量级的经验陈述)。本系列不把该数字当本机
benchmark;只提醒:seamless ≠
数学零失败,生产应以交接路径与内核 SO_REUSEPORT
能力为准。
选型收束层如何把「reload 税」写进排除树,见 第 14 篇 与 Envoy 选型:若组织变更极频繁且必须 ACK 语义,HAProxy 的文件+Runtime 模型可能先触顶;若变更以结构为主且可接受受控 reload,则 seamless 路径足够支撑经典 LB 运维。
争论的另一面:有人主张「永远不要 reload,只滚动 Pod」。在编排层这通常意味着连接仍被切断或由客户端重试吸收——把 soft-stop 责任上移了,而不是消灭了。HAProxy seamless 的价值,是在同一宿主机进程世代内完成结构切换;若平台强制不可变镜像滚动,应把 soft-stop 时间写进 Pod 生命周期,而不是以为关掉 reload 就消灭了排空问题。
运维上把 soft-stop 当成「变更预算」的一部分:每次结构 reload 占用一段双进程资源与一段排空时间;预算用尽前应合并变更、错峰执行。把十次小结构改动拆成十次 reload,比合成一次文件变更再 reload 更伤交接窗口——这与「Git 小步提交」不矛盾:提交可以碎,生效可以批。
对多 frontend 大配置,解析与 fork
成本本身也会拉长「新进程尚未接听」的体感;校验(haproxy -c)必须在发信号前完成,避免解析失败却已扰动旧进程。Master-worker
下 master
常驻,能减少「整进程从零拉起」的惊吓,但不免除配置错误导致新
worker 起不来、旧 worker 已收到 finish
的悲剧剧本——发布系统应对「新 worker
未就绪」有明确回滚或中止语义。
六、参考资料
规范 / 官方文档(A)
- HAProxy 3.4.3 Management
Guide:
https://docs.haproxy.org/3.4/management.html— graceful/hard stop;reload 与SIGTTOU/SIGTTIN;-x、-sf/-st;master-worker 与SIGUSR2;expose-fd/sockpair@。 - HAProxy 3.4.x Configuration
Manual:
master-worker、expose-fd listeners、hard-stop-after、peers、stick-tablepeers参数。
工程对照(B)
- HAProxy Technologies:Preserve stick table data when reloading HAProxy(单机 peers 抗丢表操作说明)。
- 站内 Envoy hot-restart、network/55、network/56。
源码锚点(A,路径级)
- tag v3.4.3:listener FD 传递与 master-worker reload 路径(与文档交叉核对)。
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【HAProxy 数据面】Stick-table:键类型、容量、跨线程可见性与 peers/reload
说明 stick-table 的键类型、size/expire/store、nbthread 共享内存与 nbproc 不共享的边界,以及 peers 复制与 reload 继承;覆盖表满等容量失败模式。
【HAProxy 数据面】数据面全景:从 Frontend 到 Seamless Reload 的代理内核
定位 HAProxy 相对 network/56 配置运维、Nginx 多进程与 Envoy Filter/xDS 的生态位;钉住请求路径、线程共享、协议表示、动态运维、失败归因五条坐标系,并给出 14 篇阅读路线。
【HAProxy 数据面】ACL / 规则引擎:求值顺序、副作用与 stick-table 耦合
把 ACL 与 http-request / use_backend 当作数据面机制:说明连接期到 L7 的求值顺序、动作副作用如何改写后续条件,以及与 stick-table 限流/标记的耦合点;不写 ACL 关键字百科。
【HAProxy 数据面】Health-check 引擎:探活调度、httpchk/tcp-check 与 agent-check 边界
区分健康检查引擎与数据面请求调度:说明默认 TCP 探活、httpchk/tcp-check 规则集、agent-check 的独立控制面语义,以及与 soft-stop/排空的交互提示;不写伪造检查日志。