HTX 与 HTTP
路径
之后,请求已经进入可做条件判断与改写的阶段。现场里大量「ACL
写了却不生效」并不是语法错,而是求值相位错了:连接期规则读不到
Host,或 track-sc 排在 reject
后面导致计数永远为空。本文钉规则引擎在数据面的位置与副作用,不写
ACL fetch / matcher 菜谱(见 network/56)。
本文是「HAProxy / 数据面代理内核」系列第 6 篇(共 14 篇)。→ 系列目录
篇目 核心内容 第 5 篇 · HTX / HTTP 内部表示与改写落点 第 6 篇 · ACL / 规则引擎 求值顺序与副作用 第 7 篇 · Backend / LB 选主与队列 上一篇:HTX 与 HTTP 路径 · 下一篇:Backend / server / LB
版本锚定:HAProxy 3.4.3 Starter Guide §3.4.3–3.4.4(ACL / content switching)、Configuration Manual 中
tcp-request connection/tcp-request content/http-request/use_backend;源码对照 tag v3.4.3。无伪造规则命中日志。
本篇不展开:ACL 关键字大全、map 文件运维、Lua/SPOE 扩展。选中 backend 之后的算法与队列见第 7 篇;表容量与 peers 见 第 8 篇。
一、规则引擎在路径上的位置
HAProxy 的「条件」不是单独的解释器进程,而是挂在既有会话路径上的规则列表。Starter Guide §3.4.3:条件由多个 ACL 经 AND / OR / NOT 组合;每个 ACL 由 sample fetch、可选 converter、pattern 与匹配方法构成;OR 已真或 AND 已假时短路,后续子句不求值。ACL 与 map 共享同一套内部结构,差别只是 ACL 返回 found / not found。
对数据面排障更关键的是:同一条连接会穿过多个规则面(ruleset),每个面有自己的可读样本与可执行动作:
flowchart TD
accept["accept FD"] --> rqcon["tcp-request connection"]
rqcon --> rqcont["tcp-request content"]
rqcont --> http_req["http-request rules"]
http_req --> use_be["use_backend / default_backend"]
use_be --> be_rules["backend http-request"]
be_rules --> lb["LB / stick / queue"]
| 相位 | 典型可读信息 | 典型动作 |
|---|---|---|
tcp-request connection |
源地址、端口、连接速率等;缓冲尚未分配 | accept / reject /
track-sc* / PROXY 期望 |
tcp-request content |
随缓冲填充逐步可见的 TCP/HTTP 内容 | accept / reject /
switch-mode / track-sc* |
frontend http-request |
完整 HTTP 请求(mode http) | deny / allow /
set-header / track-sc* / 变量 |
use_backend |
与 ACL 相同的 L7 样本 | 选定 backend 名 |
backend http-request |
已进入该 backend 后的请求 | 再一层改写 / 拒绝 |
Configuration Manual 对
tcp-request connection:规则按声明顺序求值;无匹配且无规则时默认
accept。对
http-request:规则在 frontend / listen /
backend
中按声明顺序求值;条件在动作执行前求值,动作只执行一次。defaults(命名段)中的规则先于所属
proxy 段。
二、use_backend:内容交换是选路,不是「再跑一遍
ACL 百科」
Starter Guide §3.4.4 把 content-based switching 说成:连接或请求到达 frontend,携带的信息经处理后,用 ACL 条件决定由哪个 backend 处理。常见模式是 Host / path 分流;也可让不同类流量走不同 LB 算法,或用 per-backend 连接限额做租户隔离。
工程上要记住三点:
use_backend列表是有序的:通常「先命中先生效」,其余不再尝试;未命中则落到default_backend(若配置)。这与「所有 ACL 同时成立再投票」不同。- 动态 backend 名可以绕过显式 ACL:用 map / sample 直接生成 backend 名。文档称此类配置有过大规模 backend 数量的生产报告;本篇不复述数字,只强调机制——选路成本从「ACL 链」转到「名字解析与 backend 对象查找」。
- 选路成功不等于上游可服务:backend
选定后仍可能因无可用
server、队列满、
timeout queue返回 503。归因要分轴,见 第 7 篇。
对照 Envoy Router:Envoy 终局给出 cluster 名;HAProxy 终局给出 backend 名。二者都是「名字选定后还有 LB / 池 / 健康集合」——不要在 frontend ACL 层假装已经完成上游可用性判断。
三、动作有副作用:顺序即语义
规则引擎最容易踩坑的地方,不是 matcher
写错,而是前一条动作改写了后一条条件所依赖的状态。手册对
http-request /
tcp-request connection
明确写了:条件在动作前求值;若多条动作依赖同一条件,第一条成功改变条件求值结果的动作,足以使后续同条件动作不再触发。官方用「从多个来源给空变量赋值」作为合法用法——反过来说,限流与放行的顺序一旦写反,就会得到完全不同的计数语义。
经典对照(语义示意,非菜谱):
# A:先 reject 再 track → 被拒连接可能从未计入 sc0
tcp-request connection reject if { src_conn_rate gt 10 }
tcp-request connection track-sc0 src
# B:先 track 再按 sticky counter 拒绝 → 速率窗口包含当前连接
tcp-request connection track-sc0 src
tcp-request connection reject if { sc0_conn_rate gt 10 }手册示例正是围绕「白名单 accept、是否在 reject 前
track」展开:先 reject 而不
track,可以把滥用源的连接速率「盖帽」为不把被拒连接算进去;先
track 再按 sc0_conn_rate
reject,则滥用源在减速之前会一直被挡住。两种都合法,取决于你要的失败模式。
同理,http-request deny 若写在
track-sc / set-var
之前,后续排障会看到「表是空的但 403 很多」——不是
stick-table 坏了,是动作顺序切断了记账路径。
副作用还包括:
- 改写请求(
set-path/set-header等)会影响同一 ruleset 中靠后的 ACL,以及之后的use_backend。 allow/deny/auth会终止或改写请求命运;写在 content switching 之前的 deny,后端永远看不到该请求。- 跨段顺序:响应路径上 backend 的
http-response先于 frontend(手册对http-response的说明),与请求路径「frontend 规则常先于选路」不对称——排障时不要假设对称。
四、与 stick-table 限流 / 标记的耦合
ACL 条件可以读取 sticky counter(sc0_* /
sc1_* / sc2_*)与表上 store
的速率、计数、GPC 等;动作侧用
track-sc*、sc-inc-gpc*
等写入。Starter Guide §3.4.5:tracking
最多可同时跟踪 3 个来自不同表的
key,且不必启用 stickiness。stickiness
另有最多 8
路并行学习源的叙述——两者不要混成「一个数字」。
耦合点可以画成:
| 步骤 | 机制 | 失败时表现 |
|---|---|---|
| 声明表 | stick-table type … size … expire … store … |
类型/容量错误 → 配置拒载或运行期插不进 |
| 绑定计数器 | track-sc0 src(或 content / http-request
相位) |
相位过早 → fetch 尚不可用;过晚 → 已被 deny |
| ACL 读表 | sc0_http_req_rate gt N 等 |
读错 sc 槽或错表 → 条件恒假/恒真 |
| 动作 | deny / tarpit /
silent-drop / 标 GPC |
只标不拦 → 日志有滥用、流量仍过 |
手册示例「frontend 用 gpc0 拒绝、backend 用
http_req_rate 检测并
sc0_inc_gpc0(http)
标记」说明:检测与拦截可以拆在不同
proxy,靠表名跨段共享。这把「ACL
菜谱」上升成分布式(同进程内)状态机——容量、过期、peers
复制则交给第 8 篇。
限流类条件还有一个数据面含义:它们把共享内存中的计数拉进每请求热路径。nbthread
下表在进程内共享(见第 8 篇);若误用历史
nbproc,每进程一张表,ACL
里的「全局限流」会静默变成「每进程限流」。配置看起来「ACL
都对」,语义已经错了。
五、短路、命名 ACL 与可观测边界
Starter Guide §3.4.3:OR 已真 / AND 已假时短路。含义是:
- 把便宜且区分度高的 fetch 放在前面(例如
path前缀),昂贵的 regex / 大 map 放后面。 - 命名 ACL 可多次声明,求值时依次尝试直到命中;文档提醒:大量命名 ACL 反而难排障,有时匿名内联 ACL 更易把上下文留在同一段配置里。
可观测上,本系列约定:无本机实跑则不粘贴伪造
show acl /
日志。排障时应先确认相位(connection vs content vs
http-request),再确认动作是否在读表之前执行,最后才怀疑
matcher。stats 上的 deny / req_denied
类计数能说明「被规则挡住」,不能单独证明「是哪一条」——需要配置顺序与(若有)日志格式中的终止状态一起看,细节见后续排障篇。
相对 Envoy
HTTP filter 链:Envoy 用 filter 顺序与
StopIteration 表达副作用;HAProxy 用 ruleset
声明顺序与 accept/deny/track
表达。二者都是「顺序即控制流」,不是「声明式策略自动收敛」。
六、内容检查延迟与「规则又跑了一遍」
tcp-request content 与 HTTP
下部分规则不是「只在连接建立瞬间跑一次」。手册写明:在 TCP
content inspection 阶段,随着请求内容更新,ACL
规则会反复求值,直到出现
accept / reject /
switch-mode,或检查延迟到期仍无匹配。这对「半包到达时
Host 还不完整」的场景是必要的,也带来两个数据面后果:
- 同一条
track-sc不要假设只触发一次记账语义而不读文档——设计限流时应以 sticky counter 的速率窗口为准,而不是「规则执行次数」。 - 昂贵 ACL(大 regex、多层 map)在缓冲填充过程中可能被多次触及,直到 accept。把重计算放进「必须等完整头部才有意义」的条件里,并合理设置 inspect delay,否则会在慢客户端上放大 CPU。
HTTP 路径上,frontend 的 http-request
在请求头可用后按声明顺序执行;随后才是
use_backend。把「依赖改写后的
path」的选路写在改写动作之前,会稳定地选错 backend——这是顺序
bug,不是 fetch 名记错。
命名 defaults 段若定义了 http-request /
tcp-request 规则,手册要求:该 defaults
不能同时被「仅 frontend 能力」与「仅
backend 能力」的 proxy 共用,listen 尤其容易踩
Ambiguity。机制含义是:规则继承不是无结构的文本粘贴,而是带能力集约束的合成。
七、谱系、争论与开放问题
谱系:内容交换与条件转发是经典 L7 LB 能力;HAProxy 把 ACL 建成与 map 同构的模式匹配核,再叠 sticky counter,使「限流 / 封禁 / 选路」共用同一套 sample 管道(Starter Guide §3.4.2–3.4.5)。这与 API 驱动代理里「独立 RateLimit filter + Router」的拆分不同——HAProxy 更强调同进程规则列表 + 共享表。
争论:配置文件里堆命名 ACL 是否可维护。官方文本已偏向「过多命名 ACL 难排障」;对立面是运维希望复用与单测式片段。没有统一正确答案,但数据面事实是:复用越多,跨相位引用越容易看错顺序。
开放问题:规则命中路径的一等可观测(「哪一条
http-request
终止了事务」)在开源工具链里仍弱于「自己在日志 format
里打标」。策略即代码(GitOps)与 Runtime 改 map/ACL
的边界,放到 Runtime API
与选型篇讨论。
八、参考资料
规范 / 官方文档(A)
- HAProxy 3.4.3 Starter Guide:§3.4.3 ACLs and conditions;§3.4.4 Content switching;§3.4.5 Stick-tables。
- HAProxy 3.4.3 Configuration
Manual:
tcp-request connection、tcp-request content、http-request、use_backend、track-sc*相关动作说明。
源码(A)
haproxy/haproxytag v3.4.3:ACL / ruleset 求值与 sticky counter 路径(include/、src/下 acl、session、stktable 相关单元)。本篇以文档语义为准,不逐行钉函数名。
站内
- network/56 HAProxy 工程(ACL 菜谱与运维)
- 第 5 篇 · 第 7 篇 · 第 8 篇 · 系列目录
- Envoy HTTP filter / Router
实验台账
- 本篇无压测、无粘贴 Runtime 输出;顺序语义来自 Configuration Manual 示例与条文。
九、小结
- ACL 是挂在会话路径上的条件核;真正决定命运的是各相位 ruleset 的声明顺序与动作。
use_backend是有序内容交换;选中名字之后仍有 LB / 队列 / 健康集合。track与reject/deny的相对顺序改变限流语义;副作用是一等公民。- 与 stick-table 的耦合经 sticky counter
完成;跨 frontend/backend
标记可行,但受表共享模型(
nbthreadvsnbproc)约束。 - content 规则可随缓冲填充反复求值;昂贵条件与 inspect delay 属于同一条性能轴。
→ 系列目录 · 上一篇:HTX 与 HTTP 路径 · 下一篇:Backend / server / LB
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【HAProxy 数据面】Stick-table:键类型、容量、跨线程可见性与 peers/reload
说明 stick-table 的键类型、size/expire/store、nbthread 共享内存与 nbproc 不共享的边界,以及 peers 复制与 reload 继承;覆盖表满等容量失败模式。
【HAProxy 数据面】Runtime API:可热改边界与必须 reload 的配置面
按 HAProxy 3.4.3 Management Guide 钉住 Runtime API 能改什么、不能改什么;区分 worker stats socket 与 master CLI,说明热改与 seamless reload 的语义边界。
【HAProxy 数据面】Seamless reload 与 soft-stop:FD 交接、排空与状态保留
按 HAProxy 3.4.3 Management Guide 拆解 -x / expose-fd listeners / master-worker sockpair 的 listen FD 交接、SIGUSR1 soft-stop 排空,以及 stick-table/peers 在 reload 下的保留边界;对照 Nginx 与 Envoy hot-restart。
【HAProxy 数据面】排障与可观测:五轴清单与 stats / sess / 日志落点
用 accept/bind、stream/mux、ACL/路由、上游/健康、reload/runtime 五轴组织 HAProxy 生产排障;说明 show info/stat/sess、日志与 stats 页面落在哪一层,不粘贴伪造转储。