做反向代理和负载均衡这些年,我经手过的方案少说也有七八种,从最早期用 Nginx 硬扛,到后来在云环境里直接用云厂商的 LB,再到自建机房里拿 LVS 和 HAProxy 做高可用组合,各有各的适用场景。但要我说最具"性价比"的解决方案,HAProxy 绝对排在前面。它不像 Nginx 那样需要操心 worker 进程和缓存配置,也不像 LVS 那样对网络环境有诸多限制,单就"HTTP 七层代理"这个领域来说,HAProxy 的专注度、稳定性和配置体验,一直是我向团队推荐的首选。如果你正在为几个后端服务要不要统一入口而纠结,或者手头有一批 HTTP 接口需要按域名、按路径分发到不同应用,那么这份深度解析应该能帮你省下不少试错时间。
这篇文章不打算讲那些官方文档里抄来的套话,而是把我从实际项目里踩过坑、验证过的理解整理成文,重点覆盖三个层面:HAProxy 为什么会选七层、几段式配置文件的内部逻辑、以及生产环境下真正值得注意的最佳实践。无论你是第一次接触负载均衡的新手,还是已经在用 Nginx 想横向对比的老手,都能从中找到可以直接落地的东西。
1. 七层代理的本质:为什么选 HAProxy
1.1 HAProxy 到底是什么
简单说,HAProxy 是一个用 C 语言写的高性能 TCP/HTTP 反向代理与负载均衡器。它最常见的部署形态是架在客户端和后端服务器之间,替后端接受所有连接请求,再按照预设策略把请求转发给真正处理业务的服务节点。
它的核心能力挂在一个词上——"分发"。客户端只需要知道 HAProxy 的地址,至于背后是两台 Tomcat 还是八台 Spring Boot 实例,对客户端完全透明。这样做的好处不仅仅是"分摊压力"这么简单,更重要的是让后端服务获得了一层统一的保护壳:可以做健康检查剔除故障节点、可以做灰度发布时的流量切分、可以在不重启业务服务的前提下完成证书更换。
在网络模型里,HAProxy 主要活跃在 OSI 七层模型的应用层(L7)和传输层(L4)上。平时我们讨论得最多的"七层代理",指的是它能够理解 HTTP 协议语义,比如 URL 路径、请求头、Cookie、请求方法这些内容,然后根据这些内容做精细化路由。这一点和只认 IP 和端口的四层负载均衡有本质区别。
举一个我在实际项目中常遇到的例子:电商系统里既有面向 App 端的 API 网关,又有面向管理后台的 Web 应用,还有给合作方用的 OpenAPI 服务。传统四层方案只能把某个端口转发到某个后端,三个服务必须用三个端口三个入口。而用 HAProxy 七层代理,可以把所有服务收敛到同一个 80/443 端口,通过路径或域名来做分发——/api/* 走网关集群,/admin/* 走后台集群,/open/* 走开放平台集群。不仅端口资源省了,对外暴露面也收窄了,安全基线更容易维持。
1.2 七层代理与四层代理的对比
很多刚接触负载均衡的朋友会把四层和七层混为一谈,实际上它们的决策信息源完全不同。
四层代理工作在传输层,只能看到 TCP/UDP 报文里的源 IP、目标 IP、源端口、目标端口。它不关心报文里装的是 HTTP 还是其他协议,转发逻辑相对简单,性能损耗也小。在 HAProxy 里用 mode tcp 开启的就是这个模式;LVS 的 DR 模式和 Nginx 的 stream 模块也属于这一类。
七层代理则要把 HTTP 报文完整解析出来,看到 URL 路径、Header 字段、Cookie 值,甚至是 POST 报文体里的内容。这就让"按业务维度做分发"成为可能。代价是 CPU 开销更高、性能上限比纯四层略低。但在今天动辄几十核的服务器上,这个差异早已不再是瓶颈。
从选型角度看,我的经验是:如果只是数据库读写分离、Redis 集群代理、或者后端协议没法统一改成 HTTP 的服务,那用四层就够了,省心省力。但如果你的服务是标准 HTTP/HTTPS,并且面临着多域名、多路径、多种后端混合部署的场景,七层代理带给你的是远超性能层面的架构灵活性。判断标准不是"哪个快",而是"哪个能让你的业务更简单地组织起来"。
1.3 HAProxy 相对同类工具的差异点
和 Nginx、LVS、云厂商 LB 相比,HAProxy 的定位有自己的特殊性。
- 与 Nginx 对比:Nginx 出身是静态 Web 服务器,后来才扩展出反向代理功能,它的优势是静态文件处理、缓存、以及丰富的第三方模块生态。而 HAProxy 从诞生第一天就是专攻"代理与负载均衡",在健康检查的精细度、调度算法的多样性、TCP 层面的异常处理上都做得更纯粹。如果你不需要做静态资源缓存,那么 HAProxy 的配置模型比 Nginx 的
upstream加server模式更直观。 - 与 LVS 对比:LVS 工作在四层,性能极高,但配置复杂,且对网络拓扑有要求(比如 DR 模式需要真实服务器也配置 VIP 的 lo 接口)。HAProxy 则完全在应用层做事,不依赖额外网卡或 IP 配置,部署灵活得多。很多大型架构会采用 LVS 做入口四层分发,后面挂一组 HAProxy 做七层精细化路由,两两互补。
- 与云厂商 LB 对比:云厂商 LB 胜在免运维,但优化空间有限,尤其在超时时间、健康检查策略、精细化日志这些维度上,你能调整的旋钮很少。自建 HAProxy 则完全掌握在手里,规则怎么写、日志怎么记,全由自己说了算。
如果非要总结一句:HAProxy 是那种"功能不做加法,但每个功能都做到极致"的基础设施组件。它不会试图取代你的 Web 服务器,也不会干涉你的业务代码,它只是把流量分发这件事做到令人放心。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 配置文件的运行模型:frontend、backend 与 listen
2.1 三段式配置逻辑
打开任何一份 haproxy.cfg,你首先注意到的往往是 global、defaults、frontend、backend、listen 这几个段。它们的组织方式其实是 HAProxy 的流量模型在配置文件里的投影。
global:进程级配置,比如运行用户、最大连接数、日志目录、SSL 库的 tune 参数。这里的改动会影响整个 HAProxy 进程的生命周期,所以默认谨慎调整。defaults:给后面的 frontend/backend/listen 提供默认参数模板,比如超时时间、模式、日志格式。相当于给整份配置定义了一种"默认口味",后面每个段落没写明的参数都会从这里继承。frontend:接收客户端连接的入口,定义监听地址、端口、以及接收请求后的处理规则。它把流量接进来,然后按规则交给对应的 backend。backend:一组真实服务器的集合,定义转发目标、负载均衡算法、健康检查方式。它是流量的出口,决定请求最终落在哪台机器上。listen:frontend 和 backend 合并在一起的简写形式,适合单组后端服务的场景。配置更短,但灵活性略低。
实际转发流程可以这样理解:客户端请求打到 frontend 的监听地址上,HAProxy 按顺序检查 frontend 里的规则,命中后把请求转给指定的 backend;backend 再通过自身的调度算法选出一台服务器,最终把请求送进去。所有规则都是"先接进来、再分出去"这样的线性思路。
2.2 调度算法怎么选
调度算法是负载均衡的核心决策点,HAProxy 在七层模式下提供了非常丰富的一组算法,我挑几个实际用得最多的来说。
- roundrobin:最简单也最常用。请求轮流分发到每个后端节点,每个节点的权重可以不同,权重高的节点分到的次数就多。适合后端机器配置接近、请求处理耗时相似的环境。要注意 HAProxy 的 roundrobin 是动态模式,支持在运行时调整权重,这一点比 Nginx 的默认轮询更灵活。
- leastconn:总是把新请求交给当前活跃连接数最少的节点。适合长连接场景,比如 WebSocket、消息推送、大量 HTTP 长轮询接口。如果后端是短请求的 API 服务,leastconn 的优势未必明显,因为连接数很快就会释放。
- source:基于客户端 IP 做 hash 取模,相同来源 IP 的请求永远打到同一台后端。适合需要会话保持的传统 Web 应用,特别是那种登录状态存在 Session 里、又没做 Session 共享的应用。缺点是节点数量变化会导致 hash 结果大面积飘移,扩容缩容时要谨慎。
- uri:基于 URL 路径的 hash,相同路径请求固定到同一节点。常见于正向代理网关做接口级缓存服务的场景。这个算法对于"同一个接口希望落到同一台机器上做本地缓存命中"的效果很明显。
- hdr:基于指定请求头的 hash,比如
hdr(host)可以按域名做路由,hdr(cookie)可以按用户的 Cookie 值做绑定。这是实现精细化会话保持的利器,但要注意目标 Header 不存在时要有兜底策略。
从我踩过的坑来说,选算法前务必要弄清楚后端应用的"状态边界"。如果后端是纯 RESTful 无状态服务,roundrobin 或 leastconn 就够了;如果后端还依赖本地缓存或内存 Session,那必须用 source 或 hdr 这类带粘性的算法,否则用户刷一下页面就被甩到另一台机器上,轻则多一次登录,重则丢失购物车数据。
2.3 健康检查:代理的眼睛和耳朵
负载均衡器最容易被忽略、却是生产环境中最不能出问题的部分,就是健康检查。HAProxy 通过定期向后端节点发送探测请求来判断节点是否存活,探测失败超过阈值就自动摘除流量,恢复后又自动加回。这一机制决定了后端故障时用户的体验是"无感切换"还是"反复报错"。
健康检查可以分为几类:
- TCP 检查:配置里最简的
option tcp-check,定期尝试连接后端端口,端口能连上就算健康。适合四层代理场景,但没法区分"端口通但业务错误"的情况。 - HTTP 检查:
option httpchk GET /healthz,通过请求一个特定的健康检查路径判断服务状态。这里的关键是后端要真的去实现一个轻量健康检查接口,而不是只检查 TCP 端口通不通。很多团队偷懒只做 TCP 检查,结果数据库挂了、后端进程还活着,流量照样往故障实例上打。 - 自定义期望响应:在 httpchk 之上配合
http-check expect status 200或者expect string来验证响应内容。即使接口返回 HTTP 200,如果响应体不是预期的文案,一样视为不健康。这个机制在检查"服务状态是否真的可用"时非常有用。
健康检查参数里有多少个容易踩坑的细节呢?inter 是探测间隔,fall 是连续失败多少次后标记节点故障,rise 是连续成功多少次后恢复节点。默认情况下 fall 为 3、rise 为 2。如果探测太快太频繁,可能在高负载时因为后端响应迟滞而误判;如果探测间隔太大,故障节点摘除就不够及时。生产环境的经验值是:inter 控制在 2~5 秒之间,fall 设 3 左右,rise 设 2~3。这样既不会对后端产生过大的探测压力,又能在几十秒内完成故障切换。
3. 配置实战:从零写一份生产可用的 haproxy.cfg
3.1 全局配置与默认配置
很多初学者上来就开始写 frontend 和 backend,忽略了 global 和 defaults 两个块,结果运行一段时间后发现日志缺失、连接数受限、性能表现怪异。接下来我直接给出一份带注释的基础配置,然后用实例讲解每个段落的作用。
haproxy复制global
log 127.0.0.1:514 local2 info
maxconn 50000
user haproxy
group haproxy
daemon
nbproc 1
tune.ssl.default-dh-param 2048
defaults
log global
mode http
option httplog
option dontlognull
option redispatch
retries 3
maxconn 40000
timeout http-request 10s
timeout queue 30s
timeout connect 5s
timeout client 1m
timeout server 1m
timeout http-keep-alive 30s
为什么 global 里要限制运行用户和最大连接数?因为它决定了一个进程能打开多少 socket、能维持多少并发会话。maxconn 50000 不是随便填的,它受到系统文件描述符上限限制——如果系统 ulimit -n 只有 1024,这里填 50000 会在启动时报错。所以在服务启动之前,务必要检查 ulimit -n,必要时在 systemd 服务文件里提高 LimitNOFILE。我习惯在部署脚本里同时执行 ulimit -n 65535 和 echo 1 > /proc/sys/net/ipv4/ip_forward(如果涉及转发)来避免上下文限制。
defaults 里的 timeout 参数被很多人低估。实际生产环境里,七层代理遇到用户报"请求卡住"或者"页面转圈",八成和超时时间设置不合理有关。timeout connect 是 HAProxy 向后端发起连接的超时,后端处理慢可能不是连接问题,但连接超时会直接报 503;timeout server 是等待后端返回响应的超时,设置太短会导致长请求(比如导出报表接口)被误杀。我见过一个数据导出系统,后端处理一次导出要 2 分钟,而默认 server timeout 只有 1 分钟,结果所有大导出全被代理掐断,用户还以为是代码 bug。所以一份生产配置中,超时时间必须在理解业务请求耗时画像后再做微调。
3.2 frontend 与 backend 的完整拆解
我们把 3.1 的配置补全成一个可运行的最小实例。这个实例模拟的场景:同一台 HAProxy 后面挂了两个后端应用,api 是给移动端用的 JSON 接口集群,web 是后台管理界面,走不同路径区分。
haproxy复制frontend http-in
bind *:80
# 默认路由到 api 集群
default_backend servers_api
# 按路径分流:/web/ 前缀走后台
use_backend servers_web if { path_beg /web/ }
# 按域名分流:admin.example.com 走后台
use_backend servers_web if { hdr(host) -i admin.example.com }
backend servers_api
mode http
balance roundrobin
option httpchk GET /healthz
http-check expect status 200
server api-1 192.168.1.11:8080 check inter 3s fall 3 rise 2
server api-2 192.168.1.12:8080 check inter 3s fall 3 rise 2
backend servers_web
mode http
balance leastconn
option httpchk GET /admin/healthz
server web-1 192.168.1.21:8080 check inter 3s fall 3 rise 2 weight 2
server web-2 192.168.1.22:8080 check inter 3s fall 3 rise 2
这里有几处值得细说。
第一,use_backend 和 default_backend 的执行顺序是有讲究的。HAProxy 按配置顺序从上到下匹配 use_backend 规则,第一条命中的生效;如果全部没命中,才会落到 default_backend。所以我习惯把更具体的路径匹配规则放前面,把宽泛的兜底放 default 里。
第二,path_beg 是 ACL 的一种。HAProxy 的 ACL 语法相当灵活,path_beg /web/ 表示请求路径以 "/web/" 开头。类似地还有 path_end(路径后缀)、path_reg(正则匹配)、hdr(host)(按请求头匹配)。前面说过七层代理的核心优势就是能理解 HTTP 内容,ACL 就是这份"理解能力"的出口。
第三,http-check expect status 200 为什么有用?因为默认的 HTTP 健康检查只看请求是否能收到响应,不关心返回码。有些后端的健康检查接口设计不合理,实际服务已经不可用但接口仍返回 200 空响应。通过 expect 显式约束响应内容,能在代理层面就拦住"假健康"的节点。
第四,weight 参数用于控制后端节点的流量比例。在 web-1 上设了 weight 2,节点之间的分配比例就是 2:1。这在灰度发布时特别好使——先让新版本以低权重接入,观察一段时间无误后,再逐步调高权重完成全量切换。
3.3 ACL 规则与高级路由
如果只是按路径分流,上面实例已经够用了。但真实业务的路由需求往往复杂得多,我挑几种高频场景给出 ACL 写法参考。
按请求方法分流——RESTful 接口里,GET 请求和 POST 请求可能走不同的后端服务:
haproxy复制acl is_get method GET
acl is_post method POST
use_backend read_cluster if is_get
use_backend write_cluster if is_post
按来源 IP 限制访问——和运维白名单结合做管理后台的 IP 访问控制:
haproxy复制acl admin_network src 10.0.0.0/8
acl is_admin_path path_beg /admin/
block if !admin_network is_admin_path
注意 block 指令在较新版本里已经改为 http-request deny。我早期用旧版本习惯了 block,升级后发现配置不兼容,排查了半天。所以如果读者用的是 2.x 以上版本,建议直接写成 http-request deny if !admin_network is_admin_path。
按 Cookie 做粘性与路由组合——用户会话标识 user_token 决定走到哪个分区,同时要求会话保持在同一个节点:
haproxy复制acl user_partition hdr_sub(cookie) -i partitionA
use_backend cluster_A if user_partition
use_backend cluster_B
这里要冷静评估一下 ACL 的匹配优先级。use_backend 的 if 条件可以连接多个 ACL,配合 or、and、! 组合完成复杂判断,但规则越复杂,排错成本越高。我的建议是:能用简单路径规则解决的问题,绝不写成三行 ACL 条件;一旦规则堆到十条以上,务必用 HAProxy 自带的统计页面观察流量分发是否符合预期,别等到线上出问题了再一行一行测试。
3.4 会话保持与 SSL 终结
会话保持(Session Persistence)在七层代理里是个绕不开的话题。前文提到,source 算法能基于 IP 做粘性,但它的问题是客户端 IP 如果有大范围代理(比如公司出口统一 NAT),会打得很不均匀。所以七层下更推荐用 Cookie 来做会话保持。
HAProxy 支持两种 Cookie 干预方式:被动解析 Cookie 或者主动注入 Cookie。第一种方式是应用本身已经在请求里携带了会话 ID,HAProxy 只需要解析这个 ID 并 hash 到固定节点。第二种方式是通过 cookie 指令让 HAProxy 自己生成为一种会话粘性 Cookie,并在每次转发时重写它。
实际配置示例:
haproxy复制backend servers_api
mode http
balance roundrobin
cookie SRV insert indirect nocache
option httpchk GET /healthz
server api-1 192.168.1.11:8080 cookie s1 check inter 3s
server api-2 192.168.1.12:8080 cookie s2 check inter 3s
这段配置会让 HAProxy 在首次响应时给客户端种一个名为 SRV 的 Cookie,值对应被选中的节点(s1 或 s2)。客户端后续请求带上这个 Cookie,HAProxy 一读便知该转发给谁,实现精确到节点级的会话保持。insert 表示由 HAProxy 插入 Cookie,indirect 表示请求里如果已经有同名 Cookie 就不再重复插入,nocache 避免中间缓存设备缓存带 Cookie 的响应。
另一个现代 Web 服务绕不开的点是 HTTPS。HAProxy 做 SSL 终结指的是:客户端到 HAProxy 走 HTTPS,HAProxy 解密后向后端转发走 HTTP。这样做的好处是证书仅部署在代理层,后端服务不需要各自处理证书,极大简化了证书管理工作。
配置示例:
haproxy复制frontend http-in
bind *:80
bind *:443 ssl crt /etc/haproxy/certs/example.pem
http-request redirect scheme https unless { ssl_fc }
default_backend servers_api
其中的 crt 参数指定 PEM 证书文件路径。HAProxy 要求证书和私钥放在同一个 PEM 文件里,顺序是证书在前、私钥在后。我第一次部署时不知这个惯例,把两者分开两个文件放,结果 HAProxy 一直报证书解析失败。后来才知道它需要单文件聚合。如果有多个域名共用一个 HAProxy,crt 参数可以填目录路径,HAProxy 会加载目录下所有 PEM 文件,并根据客户端 SNI 自动匹配证书。
如果后端服务也需要走 HTTPS,可以在 server 行添加 ssl 和 verify none:
haproxy复制server web-1 192.168.1.21:8443 ssl verify none check
3.5 开启统计页面观察流量
HAProxy 自带一个统计监控页面,可以实时查看每个 frontend/backend 的连接数、队列、会话速率、健康状态。这是我调优和排错时的第一落点。
haproxy复制listen stats
bind *:8404
mode http
stats enable
stats uri /haproxy-stats
stats realm Haproxy\ Statistics
stats auth admin:your_strong_password
stats refresh 5s
stats hide-version
几点细节说明:
stats auth是基础认证,一定不要用弱口令,因为统计页面上能看到所有后端的真实 IP 和端口。stats refresh 5s控制页面自动刷新间隔。stats hide-version隐藏 HAProxy 版本号,避免被扫描工具精准识别漏洞版本。- 统计页面的字段里,
Session rate和一分钟内新建连接数能反映出流量峰谷;Queue表示排队等待的请求数,如果长期不为 0,说明后端处理能力已经跟不上,需要扩容。
这个页面在我排查"为什么用户间歇性 503"的时候帮过大忙。有一次后端某个节点内存泄漏导致频繁 OOM,虽然健康检查还没触发剔除(因为进程还活着、健康接口短暂通了一会),但统计页面上能看到该节点的 Sessions 数值异常偏高,排查效率瞬间翻倍。
4. 生产环境里的坑与最佳实践
4.1 超时时间再细致一点
很多人用 HAProxy 一年,还是用着一层不变的默认超时。但生产环境中超时参数恰恰是离用户体感最近的东西。下面按照请求的完整生命周期拆开说明。
timeout http-request:从 client 连接建立到 HAProxy 完整接收 HTTP 请求头的时间。设短一点(5~10s)可以防止慢连接攻击占用连接资源。遇到有人恶意缓慢发包时,这个超时是第一道防线。timeout connect:HAProxy 与后端建立 TCP 连接的超时。后端在高负载下可能出现 accept 队列堆积,这时 connect 超时会频繁触发。设 3~5 秒比较合理,设太长会导致代理侧积累大量等待请求。timeout queue:负载均衡器发现所有后端节点都已达到 maxconn 时,新请求会在队列里等待。这个值设太长会拖垮用户体验,设太短又会快速返回错误,常见设置为 30~60 秒。timeout client和timeout server:这两个对应的是客户端和后端在传输数据过程中的空闲超时。如果是普通 Web 页面,1 分钟足够;如果存在长轮询接口或 WebSocket 连接,必须调大甚至设置为 1 小时。WebSocket 应用如果没调大 server timeout,连接断掉时表面上浏览器还显示在线,实际消息已经收不到了。
调整超时值有没有一个万能公式?没有。但有一个基本思路:按"后端最慢合法请求的耗时的 1.5~2 倍"来设 server timeout。比如导出功能最慢需要 120 秒,那 server timeout 至少设 180 秒;如果最慢需要 2 分钟,实际设 3 分钟是比较稳妥的。不要为了"快速失败"把 server 超时压得太低,那是自己给自己找故障。
4.2 日志与监控:别等到排错时才想起来看
HAProxy 默认不开启详细日志,需要先在 global 配置里指定 log 目标和 socket,再在 defaults 里设置 option httplog,日志才会按 HTTP 请求维度输出。输出内容包括:客户端 IP、请求时间、请求行、状态码、后端节点、响应耗时、字节数等。
生产环境里我习惯把 HAProxy 日志单独发到一个独立日志服务器,通过 syslog 协议送出去:
haproxy复制global
log 192.168.10.10:514 local2 info
本地则交给 systemd 或 rsyslog 处理。常见做法是在 /etc/rsyslog.d/haproxy.conf 里添加规则,把 local2 的日志写到 /var/log/haproxy.log。
日志的价值在排查"响应慢"这类问题上尤其明显。之前有一次用户反馈某个接口经常出现 10 秒以上的延迟,业务方说是代理慢,代理说是业务慢。僵持不下时,我查看 HAProxy 请求日志里的 Tq(请求传输时间)和 Tw(等待时间),确认 HAProxy 在几十毫秒内就完成了转发,真正耗时发生在后端 Tt 里。问题定位直接指向业务服务,代理侧得以洗清嫌疑。没有日志,这些争论就只能靠感觉拍板。
监控方面,HAProxy 统计页面其实已经给出了足够全面的指标,但它不具备时间序列存储能力。我通常用 Prometheus 的 haproxy_exporter 把指标拉进监控系统,配合 Grafana 做曲线展示。重点盯这几个指标:后端集群的 current sessions、每秒会话创建速率、队列长度、健康检查失败次数。任何一个指标出现持续抬升或抖动,都要尽快排查是不是有节点进入不健康状态。
4.3 健康检查引发的血案:一次误伤记录
健康检查是代理层保护后端的重要手段,但配置不当也会误伤业务。这里说一个我自己踩过的经典坑。
有一次线上系统出现间歇性 502 和节点无规律抖动。查看 HAProxy 日志,发现某后端节点的 check 频率异常高,同一个节点反复出现 down/up 切换。后来定位到问题根源:该后端节点的健康检查接口 /healthz 内部会实时查询数据库状态,而数据库同一时刻正好在做一次短暂的主从切换,导致健康接口在几十秒内返回超时。HAProxy 的健康检查连续失败 3 次,就把节点摘掉了;数据库切换完成后,节点恢复,又被加回来。整个过程中用户请求不断在可用的节点之间切换,部分请求因为恰好在摘除瞬间到达而报错。
这个案例给我的教训有三条:
第一,健康检查接口必须轻量。/healthz 只应检查进程存活和必要依赖,不应该做重量级数据库查询。真正要检查数据库依赖时,可以在业务服务内部维护一个异步探测结果,把状态缓存几秒后再暴露给健康检查接口。
第二,摘除节点要有浪涌缓冲。很多生产环境会把 fall 参数从 3 改到 6 或更高,这样即使健康检查偶发失败,也不会因为一两次误判就把节点摘掉。同时把 rise 设小一点,让恢复节点尽快接回来。
第三,实施负载均衡的高可用部署时,健康检查策略必须和业务团队充分对齐。你以为是"保护",对方可能觉得是"乱摘节点"。提前制定好服务不可用判定标准,再写进检查逻辑里。
4.4 高可用与安全加固要点
一个 HAProxy 实例如果宕机了,负载均衡层本身就成了单点。所以生产环境至少要做成主备或双活模式。最常用的方案是与 Keepalived 配合,使用虚拟 IP(VIP)做漂移:正常情况下 VIP 绑定在主节点上,主节点故障后,备用节点接管 VIP,实现秒级切换。
Keepalived 与 HAProxy 的整合要点是:Keepalived 里的 vrrp_script 要周期性地检测 HAProxy 进程状态,比如执行 kill -0 $(cat /var/run/haproxy.pid) 检测进程是否存在。进程没了,就触发优先级降低,把 VIP 让给备节点。更精细的做法是同时用 haproxy -c 做配置检查,避免进程活着但配置已损坏的状态被当成健康。
安全加固方面,下面几条是我在新部署时必做的:
- 最小化监听端口:只用 80 和 443 对外服务,统计端口绑定内网 IP 或者加 ACL 限制来源网段。
- 隐藏版本信息:在
defaults里添加option httplog时记得stats hide-version,同时在错误页面不要泄露 HAProxy 版本。 - 限制单 IP 连接数:通过
stick-table或maxconn限制单 IP 的并发连接,能在一定程度上缓解 CC 攻击压力。比如一个来自同一 IP 的连接数超过 50 后直接返回 429。 - 合理设置
maxconn:这个值不光受限于文件描述符,还受限于后端处理能力。后端是 4C8G 的实例,代理却允许 5 万连接涌过去,后端一打就满。最好的做法是让 maxconn 略低于后端集群的极限承载,起到削峰缓冲的作用。
高可用层面还要考虑配置同步。Keepalived 只管 VIP 漂移,不会帮我同步 haproxy.cfg。如果修改了主节点的配置,备节点的配置不一定同步。所以通常还要搭配一套配置分发机制(比如 Git + 部署脚本,或者 Ansible 批量分发),确保主备节点配置完全一致,否则切换后行为不一致,排错会非常痛苦。
4.5 常见故障排查速查表
最后整理一份高频问题速查表,这些场景我基本都在生产环境里遇到过,可以直接对照处理。
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 连接建立后立即 503 | 后端所有节点处于 down 状态 | 先看统计页面,再直接 curl 后端的健康检查接口 |
| 间歇性 502/504 | 后端响应超时或主动断开连接 | 查看 httplog 中的 Tt 字段和后端日志,确认耗时瓶颈在哪端 |
| 用户请求总是打到同一台节点 | source 算法 + 公司出口 NAT 导致 hash 集中 | 改用 roundrobin 或 leastconn,并结合 Cookie 会话保持 |
| 健康检查频繁 up/down 抖动 | 健康检查接口执行的检查太重,或后端依赖组件短暂抖动 | 简化健康检查逻辑,调大 fall 数值 |
| 修改配置 reload 失败 | haproxy.cfg 语法错误或 bind 端口被占用 | 先跑 haproxy -c -f /etc/haproxy/haproxy.cfg 做语法校验 |
| SSL 证书访问报错 | PEM 文件格式错误或证书与私钥不匹配 | 用 openssl x509 -in cert.pem -noout -text 验证证书,检查 PEM 自包含顺序 |
| 日志没有请求详情 | defaults 里缺少 option httplog 或 log 未指向日志服务器 |
补全 defaults 配置,并确认 syslog 接收端可达 |
| 高并发下 CPU 飙升 | 节点数过少、SSL 握手消耗、或者被 CC 攻击 | 观察统计页面的会话速率,结合 top 看 haproxy 进程占用的 CPU |
其中 haproxy -c 是我几乎天天要敲的命令。修改配置后不做语法检查就 reload,一旦语法错误,HAProxy 会拒绝加载新配置,但旧进程已经停掉,线上服务直接就断了。这类事故完全可以通过"先 -c 再 reload"两条命令避免掉。所以我在所有部署脚本里都会强制加上 haproxy -c -f 步骤,没有这步的自动化部署流程在我这里是过不了评审的。
4.6 一点个人体会
做了这么多年流量代理,我最深的感触是:负载均衡器这个角色,平时感受不到存在感,一切正常;一旦出问题,它就是背锅第一候选。业务卡了说代理慢,接口挂了说代理断了,用户体验差说代理不会配。所以建设一套稳定可观测的代理层,表面上是在维护基础设施,实际上是在为整个研发团队的排查效率托底。
从技术选型角度,HAProxy 依然是我在新项目里的默认起点。它的配置模型简单直白,性能足够覆盖绝大多数场景,加上社区长期活跃、文档完善,踩坑时的自救成本很低。如果你正在规划一套统一入口,不妨从最小的 frontend + backend 开始跑通,然后逐步完善健康检查、日志和统计监控,最后再加入 Keepalived 高可用。这套路径我自己走过多次,每一步都能看到收益,不会走弯路。
最后分享一个小技巧:每次上线前,除了语法检查,我会用 haproxy -vv 查看编译信息,确认打开了 PrometheusExporter、SSL、PCRE 等模块。有一回我部署好以后怎么配都不生效,检查半天发现装的是 minbuild 版本,缺了关键模块。版本对了、模块全了,配置才可能按照预期跑起来。这个细节放到现在也值得再三确认。
