在生产环境里跑过 HAProxy 的人,十有八九都被超时问题折磨过。用户说接口偶尔卡死、连接池被占满、后端一台机器明明活着却被流量打挂,翻配置文件一看,全是默认超时,负载均衡算法也是随手写的 roundrobin。这些问题不是玄学,就是超时配置和负载均衡策略没有跟着业务形态走。
这篇文章我打算把 HAProxy 里最容易被忽视、也最容易踩坑的两块东西讲透:一是 timeout 系列参数到底该怎么设,二是负载均衡算法怎么选才不翻车。顺便给一份可以直接抄作业的配置模板,把我自己在生产环境里验证过的参数组合和踩坑记录都放进去。适合正在用 HAProxy 做入口网关、又对超时和调度逻辑没有系统梳理过的同学,尤其是后端有 Java/PHP/Node 混合服务、需要同时处理长连接和短请求的场景。
1. 先搞懂 HAProxy 的超时配置到底在管什么
1.1 超时的本质:一条请求链路上的三个环节
很多人把超时配置当成“防卡死”的兜底开关,觉得设得越大越安全,设得越小越高效。这个理解太粗了。HAProxy 的超时配置,本质是在管理一条完整请求链路上三个不同环节的等待时间:客户端到 HAProxy、HAProxy 到后端服务器、以及 HAProxy 内部处理单个 HTTP 请求的时间预算。
打个比方,HAProxy 就像公司前台。来访者(客户端)进门后等待前台接待的时间,对应 timeout client;前台把访客引到业务部门后,业务部门迟迟不给回复,访客愿意等多久,对应 timeout server;而大厅里排队的人太多,前台规定每个人必须在多久内把自己的诉求讲清楚,否则先请出去,这就是 timeout http-request。
这三段超时既独立又联动。如果你只调大了 timeout server,但 timeout client 没跟着调整,就会出现后端还在慢慢处理,客户端那边先等不及断开了,HAProxy 只能把后端已经算好的结果丢掉,白白浪费一次计算。反过来,timeout client 设得很大、timeout server 很短,又会导致客户端一直吊着连接,后端早就超时返回了,两边状态对不上。
1.2 核心超时参数逐个拆解
我把 HAProxy 里最常用的几个超时参数列成了一张表,每个参数对应我上面说的哪一个环节,以及实际业务里常见的问题,一目了然。
| 参数 | 默认值 | 作用环节 | 典型坑 |
|---|---|---|---|
| timeout connect | 5s(老版本 10s) | HAProxy 与后端建立 TCP 连接 | 后端起服慢或跨机房时容易误判 |
| timeout client | 50s(部分版本) | 客户端与 HAProxy 之间的空闲超时 | 长轮询、SSE 推送会被掐断 |
| timeout server | 50s(部分版本) | HAProxy 与后端之间的空闲超时 | 慢 SQL、复杂计算接口频繁 504 |
| timeout http-request | 10s(部分版本) | 客户端发送完整请求头的时间 | 大文件上传、弱网环境会失败 |
| timeout http-keep-alive | 10s(部分版本) | keep-alive 连接的空闲保留时间 | 有人设 0 导致连接频繁重建 |
| timeout check | 默认跟随 server | 健康检查等待后端响应的时间 | 健康检查超时太短导致误杀节点 |
| timeout tunnel | 无默认 | WebSocket / CONNECT 隧道类连接 | 不设置则长连接说断就断 |
注意,不同 HAProxy 版本的默认值不完全一样,我上面标注的是比较常见的默认情况。真正的关键不是背默认值,而是理解每个参数的语义,然后结合你的业务特点去显式配置——我强烈建议你不要依赖默认值,因为默认值通常不适合直接拿到生产环境用。
timeout connect 和网络质量、后端启动速度强相关。如果后端服务是 Java 应用,启动就要 40 秒,而 HAProxy 只给 connect 5 秒,那么在服务重启期间,所有流量都会被 HAProxy 判定为“连接失败”,直接打到其他节点,如果所有节点都在重启,整个服务就雪崩了。所以 connect 超时最好设置成大于后端最大启动时间的一半,同时配合健康检查的 rise 参数,给后端留出预热窗口。
timeout http-request 是一个容易被低估的参数。它限制的是客户端从建立连接到发送完完整请求头的时间,不是整个请求的耗时。对正常浏览器来说,这个时间一般几十毫秒就完成了,设 10 秒完全够。但如果你的业务涉及大文件上传,或者有客户端在弱网环境下访问,请求头发送时间可能被拉长,这时候就需要适当调大。我见过一个真实案例,某系统上传 200MB 以上的文件时频繁失败,排查到最后发现就是 http-request 超时设得太短,客户端还在慢慢传头部,HAProxy 就掐断了连接。
timeout client 和 timeout server 都是“空闲超时”,不是“请求总耗时超时”。这个语义一定要分清。比如 timeout server 设 30 秒,意思是后端在 30 秒内没有返回任何数据,HAProxy 就断开连接。但如果后端一直在一点一点地吐数据,哪怕整个请求花了 5 分钟,只要中间没有超过 30 秒的空档,HAProxy 就不会断。理解了这一点,你就知道慢接口和长连接的区别了——慢接口是单个响应耗时久,长连接是连接建立后长时间没有数据传输,这两类场景对 timeout server 的要求完全不同。
2. 负载均衡算法选型:别让随机轮询毁掉你的服务
2.1 从“等开销”说起:HAProxy 的常用算法
负载均衡算法是 HAProxy 里另一个高频词。热词榜上有个“等开销负载均衡”,指的其实是最小连接数(leastconn)这类动态调度算法——它追求的是让每个后端节点承担的负载尽可能相等,而不是让每个节点接收的请求次数相等。
HAProxy 的常用算法我分成两类。第一类是静态算法,比如 roundrobin、static-rr、first,它们在配置加载时就确定了转发规则,不依赖后端实时状态。第二类是动态算法,比如 leastconn、random、source、uri、hdr,它们会根据连接数、请求特征或客户端信息做实时决策。
roundrobin 是最经典的轮询算法,请求按顺序轮流分配给每个后端节点。它的优点是实现简单、公平性好,每个节点拿到的请求数基本一致。缺点也很明显:它只保证“请求数量”的均等,不保证“负载”的均等。假设后端有 A、B 两台机器,A 性能强,B 性能弱,A 处理一个请求只要 100ms,B 要 500ms,用 roundrobin 轮询,B 很快就会积压大量并发请求,而 A 却在空转。反过来,如果 A、B 性能相同,接口耗时差异又很大——有的请求是普通查询,有的请求是复杂报表——roundrobin 也没法区分,慢请求一旦集中在某台机器上,同样会拖垮单点。
leastconn 就是我上面说的“等开销”思路的典型实现。它每次都把新请求分配给当前活跃连接数最少的后端节点。这个算法特别适合长连接场景,比如 WebSocket、数据库连接池、消息推送服务,因为在这些场景里,连接数能比较真实地反映节点的负载。但 leastconn 也有自己的问题:如果请求本身非常短、处理极快,leastconn 的“最少连接数”几乎总是落在刚刚处理完上一个请求的节点上,会导致新连接集中打向同一台机器,反而失去均衡效果。
source 算法根据客户端 IP 做哈希,同一个 IP 的请求总是转发到同一个后端。它天然实现了会话保持,适合那些没有做分布式会话、必须粘在某一台机器上的老系统。缺点是如果某个 IP 下面挂着大量用户(比如公司出口 NAT),流量会全部压在同一个后端上,负载非常不均衡。uri 算法则根据请求的 URI 做哈希,适合做缓存场景,同一个接口的请求固定打到同一台缓存服务器,可以提高缓存命中率。
2.2 算法背后隐藏的语义:会话保持与一致性哈希
选负载均衡算法,很多时候不是在选“谁更均衡”,而是在选“你接受哪种不均衡”。这句话是我做了多年负载均衡之后最深的体会。
举几个具体场景。后端是普通的无状态 API 服务,接口响应时间差异不大,并发量中等,直接用 roundrobin 就够了,配置简单,问题也少。后端是有状态服务,比如需要保存用户登录 Session、而且没有做 Session 共享,那你就必须用 source 或配置 cookie 粘性,否则用户刷新页面跳到了另一台机器,登录态就丢了。后端是 WebSocket 或 TCP 长连接服务,连接建立后要长期占用,leastconn 是最合理的选择,因为它能直观反映每台机器的连接承载量。
还有一种情况容易被忽略:后端节点配置不同,比如一台 4 核 8G,另一台 8 核 16G。这时你可以在 HAProxy 里给节点设置 weight 权重,让高性能节点分到更多流量。roundrobin 和 leastconn 都支持 weight,默认是 1,你可以把高性能节点设成 2,相当于它接收的流量是普通节点的两倍。
还有一个容易踩的坑:一致性哈希。如果你用 uri 或 source 这类哈希算法,在后端节点数量变化时(比如扩缩容),哈希映射会大规模重新分布,导致大量请求突然打到不同的后端,缓存命中率暴跌,甚至引发缓存雪崩。HAProxy 对 source 和 uri 都有 hash-type 参数,默认是 map-based,节点变化影响面较大;你可以改成 consistent,也就是一致性哈希,这样节点增减时只会影响一小部分映射关系。我自己的习惯是:只要用了哈希类算法,就强制加上 hash-type consistent。
2.3 一张表帮你选算法
我不喜欢给“最优算法”这种结论,因为脱离业务谈算法都是耍流氓。但我可以把常见业务场景和推荐算法列成一张表,大家可以对号入座。
| 业务场景 | 推荐算法 | 理由 |
|---|---|---|
| 普通短请求 API,后端无状态 | roundrobin | 简单可靠,请求数均等 |
| 后端性能差异明显 | roundrobin / leastconn + weight | 用权重调节流量比例 |
| WebSocket / 消息推送 / 数据库中间件 | leastconn | 连接数能反映真实负载 |
| 老系统需要 Session 粘性 | source 或 cookie 粘性 | 同一客户端固定访问同一节点 |
| 缓存服务,按 URL 做哈希 | uri + hash-type consistent | 相同请求命中相同缓存节点 |
| 请求处理时间差异极大 | leastconn | 避免慢请求压垮单机 |
| 流量突发,追求简单稳定 | random | 2 个参数即可,分配均匀 |
讲完算法,我得特意提一句 nginx。很多人会拿 HAProxy 和 nginx 做负载均衡对比。nginx 的 upstream 默认是加权轮询,也支持 least_conn、ip_hash、url_hash 等,七层路由能力(按域名、路径转发)比 HAProxy 灵活得多。但 HAProxy 胜在四层和七层通吃,TCP/UDP 流量也能直接代理,而且资源占用极低,单机并发能力非常强,配置语义也更贴近“负载均衡器”这个定位。所以我的习惯是:需要精细的路径路由、缓存、静态文件服务,前面挂 nginx;需要高性能四层转发、TLS 终结、TCP 长连接调度,用 HAProxy。两者不是替代关系,而是搭档关系。
3. 一份可直接抄作业的 HAProxy 配置
3.1 安装与基本结构
HAProxy 的安装很简单,主流 Linux 发行版的软件源里都有。Debian/Ubuntu 上用 apt install haproxy,CentOS/RHEL 上用 yum install haproxy。装完后配置文件在 /etc/haproxy/haproxy.cfg,服务管理用 systemctl。
配置文件的整体结构分四大段:global 段设置进程级参数,defaults 段设置默认参数,frontend 段定义入口监听,backend 段定义后端服务器组。另外还有 listen 段,适合把 frontend 和 backend 合并写在一起,比如暴露一个端口就转发到一组后端,用 listen 最简洁。
我第一次写 HAProxy 配置时犯过一个低级错误:以为 defaults 里的参数会被 frontend 和 backend 自动继承,就只在 defaults 里配了超时,结果 frontend 里有些参数覆盖了 defaults,行为变得很迷。实际规则是:frontend、backend、listen 里的配置优先于 defaults,未显式声明的参数才继承 defaults。所以你在 defaults 里配好的超时,只要 frontend/backend 没覆盖就会生效;但如果 frontend 里配了 timeout client,backend 里没配 timeout server,那么 server 超时依然走 defaults,这个混合生效的机制非常容易让人排查得一头雾水。
3.2 完整配置示例(含超时与负载均衡)
下面这份配置是我在一个真实项目里用的模板,业务场景是:前置 HAProxy 统一接收 HTTP 流量,后端有 4 台无状态 API 服务,接口平均响应时间 300ms,个别报表接口会到 5 秒,整体并发量不大,但偶尔有来自内部系统的长轮询请求。
global log /dev/log local0 log /dev/log local1 notice maxconn 3000 user haproxy group haproxy daemon stats socket /var/run/haproxy.sock mode 600 level admin tune.ssl.default-dh-param 2048 defaults log global mode http option httplog option dontlognull option http-server-close option forwardfor except 127.0.0.0/8 retries 3 timeout connect 5s timeout client 30s timeout server 30s timeout http-request 10s timeout http-keep-alive 10s timeout check 5s frontend web_http_in bind *:80 # 按域名分流到不同后端 use_backend api_servers if { hdr(host) -i api.example.com } use_backend longpoll_servers if { hdr(host) -i push.example.com } default_backend api_servers backend api_servers balance leastconn option httpchk GET /healthz http-check expect status 200 server api-01 10.0.0.11:8080 weight 1 check inter 3s fall 3 rise 2 server api-02 10.0.0.12:8080 weight 1 check inter 3s fall 3 rise 2 server api-03 10.0.0.13:8080 weight 2 check inter 3s fall 3 rise 2 server api-04 10.0.0.14:8080 weight 1 check inter 3s fall 3 rise 2 backend longpoll_servers balance leastconn option httpchk GET /healthz timeout server 60s server push-01 10.0.0.21:8080 check inter 3s fall 3 rise 2 server push-02 10.0.0.22:8080 check inter 3s fall 3 rise 2几个关键点一个一个说。
balance leastconn 的选择逻辑我在上一段讲过:接口耗时差异大,leastconn 比轮询更稳。api-03 的 weight 设成 2,是因为这台机器是 8 核 16G,其他三台是 4 核 8G,用权重把多出来的性能吃满。weight 只影响新连接分配,不影响已建立的连接,所以调整权重后要等一段时间才能看到流量比例变化,不是立刻生效。
timeout client 和 timeout server 都设成 30 秒,而不是默认的 50 秒。原因是这个项目的业务接口集中在 5 秒以内,30 秒已经留了充足的缓冲;设短一点的好处是,当后端线程池被打满时,HAProxy 能更快地切断僵尸连接,避免连接堆积占用内存和文件描述符。但是注意,longpoll_servers 这个后端单独设置了 timeout server 60s,因为长轮询请求最长会挂 50 秒才有响应,如果沿用 30 秒,所有长轮询都会被提前掐断。这正好说明了为什么超时配置不能全局一刀切,不同后端要分开设。
timeout http-request 10s 是给客户端发送请求头的宽限时间。正常情况下足够,但如果你的业务有大文件上传,我建议单独给上传接口所在的 backend 调大这个值,或者用 http-request set-timeout 指令根据请求特征动态调整。后面我会专门讲这个技巧。
3.3 超时与负载均衡如何联动:session 超时、连接复用与粘性
很多人把超时配置和负载均衡算法当成两个独立的话题,这不对。超时和算法之间有一条隐藏的联动关系,不理解这条线,生产环境早晚会出问题。
先说连接复用的问题。HAProxy 默认开启 keep-alive 支持,客户端可以通过一个 TCP 连接发送多个 HTTP 请求,减少握手开销。timeout http-keep-alive 控制了 keep-alive 连接在空闲时的保留时间。如果这个值设得太小,客户端刚发完一个请求、下一请求还没发出来,连接就被 HAProxy 关了,客户端要重新建连;设得太大,又会让大量空闲连接占用 HAProxy 的并发槽位。我在生产环境一般设 5 到 10 秒,配合 option http-server-close,让 HAProxy 和后端之间的连接在请求结束后主动关闭。这样做的好处是:后端不需要维护大量 keep-alive 连接,线程模型简单的服务(比如 PHP-FPM)会更省资源。代价是每次请求都要重新建立 HAProxy 到后端的 TCP 连接,增加了毫秒级的延迟。如果后端长连接很贵(比如数据库连接池),你就不该开 http-server-close,反而应该让 HAProxy 尽量复用上游连接。
再说会话保持和超时的关系。如果你用的是 source 或 cookie 粘性做会话保持,客户端和某个后端节点的绑定关系会一直存在。这时如果 timeout client 设得过短,客户端连接断开,粘性绑定会随连接结束而失效,用户下次请求可能被分配到另一台机器。对于无状态服务这不是问题,但对于需要在节点本地缓存数据的场景,这会导致缓存命中率下降。
最后说健康检查和负载均衡的配合。健康检查不仅负责剔除宕机节点,还会影响新连接分配。假设后端某台机器的健康检查超时设成 5 秒,但实际接口偶发 8 秒才响应,HAProxy 会认为这台机器挂了,把流量全部转移到其他节点,然后其他节点被压垮,形成雪崩。所以健康检查的超时必须比后端最慢的“正常响应”时间还要宽松,同时配合 fall 次数,避免一次抖动就误杀节点。我惯用的参数是 check inter 3s fall 3 rise 2,意思是每 3 秒检查一次,连续 3 次失败才标记为宕机,连续 2 次成功才恢复。这样能滤掉偶发抖动,又不会让故障节点在流量里停留太久。
3.4 配置验证与热加载:别用 restart 打断线上连接
HAProxy 配置改完后,一定要先验证再加载。验证命令是 haproxy -c -f /etc/haproxy/haproxy.cfg,如果语法有误,它会直接告诉你哪一行出了问题。这一步必须养成习惯,不要跳过去,否则一个手滑的缩进错误可能让整个服务起不来。
加载配置时,强烈建议用热加载而不是 restart。热加载的方式有两种:一是 systemctl reload haproxy,二是通过 stats socket 执行 haproxy -sf 发送平滑过渡信号。热加载的本质是启动一个新的 HAProxy 进程,新进程接管新连接,旧进程继续服务完已有的存量连接后再退出。由于 HAProxy 使用的是 SO_REUSEPORT 特性,新旧进程可以短暂并存,连接不会中断。相比之下,restart 会直接杀掉旧进程,所有在途请求瞬间断开,长连接服务秒级不可用。我在第一次做线上配置变更时就是因为用了 restart,导致正在跑的 WebSocket 连接全部断线,被业务方追着骂了一个下午。
为了热加载后能排查问题,建议在 global 段打开 stats socket:stats socket /var/run/haproxy.sock mode 600 level admin。有了这个 socket,你可以用 echo "show info" | socat stdio /var/run/haproxy.sock 查看运行时信息,用 echo "show servers state" 查看后端节点状态,甚至可以动态调整节点的 weight 和启用/禁用节点,而不需要改配置重载。后文排查问题会用到这些命令。
4. 真实场景里的坑:长连接、慢接口、健康检查
4.1 健康检查超时引发的“假宕机”
健康检查超时是我见过最多人踩坑的地方。很多人配置健康检查时,只关注 inter(检查间隔)、fall(失败多少次算宕机)、rise(成功多少次算恢复),却忽略了 timeout check。timeout check 的默认值一般跟随 timeout server,如果 timeout server 是 30 秒,timeout check 也是 30 秒,看起来没什么问题,但在某些版本的 HAProxy 里,timeout check 默认只有 5 秒。
想象一个场景:后端有一个查询接口,平均耗时 2 秒,但偶尔因为数据库锁等待,会到 8 秒才返回。如果你用这个接口作为健康检查路径,healthz 本身可能也会慢。HAProxy 发起健康检查后,5 秒内没收到响应,就记录一次失败;连续 3 次失败后,节点被标记为下线。但实际上,后端只是偶发慢响应,并没有宕机。节点被摘除后,流量全部压到其他节点,其他节点压力变大,响应变慢,健康检查也开始超时,一个接一个被摘除,最后整个集群雪崩——而最初的起因只是健康检查超时设得太短。
我的处理办法分两层。第一层,健康检查路径用专门设计的轻量接口,这个接口只检查进程存活和数据库连接池是否可用,不执行复杂逻辑,保证 100ms 内返回。第二层,timeout check 显式设成不小于 3 秒,配合 inter 5s 和 fall 3,既保证快速发现故障,又不会因为单次抖动就误杀节点。如果你的后端确实没有轻量健康检查接口,只能靠业务接口做健康检查,那 timeout check 必须大于业务接口的 P99 响应时间,宁可多等几秒,也不能误判。
4.2 慢接口与长连接场景的超时处理策略
慢接口和长连接是最容易让超时配置“打架”的两类场景。它们的共同点是:连接持续时间长,数据流动慢。不同点是:慢接口是一次请求要执行很久,长连接是连接建立后长时间没有数据传输。
慢接口场景,timeout server 必须设置成大于接口最坏情况下的执行时间。比如一个导出报表接口,最慢要跑 90 秒,timeout server 至少要设 100 秒以上。但你不能把所有后端都设成 100 秒,否则其他普通接口的僵尸连接会占用大量资源。更好的做法是给慢接口单独划分一个 backend,设置独立的 timeout server,然后用 ACL 把特定路径转发过去。HAProxy 还支持 http-request set-timeout,可以在请求级别动态修改超时值:
frontend web_http_in bind *:80 # 动态调整慢接口的超时时间 http-request set-timeout server 120s if { path_beg /export/ } default_backend api_servers这个指令非常实用,等于你在请求进入时就告诉 HAProxy:“这个请求可能很慢,给它 120 秒忍耐时间。”我强烈建议所有有慢接口的业务都加上这类配置,比全局调大 timeout server 精准得多。
长连接场景,比如 WebSocket、SSE 推送、TCP 隧道,问题完全不一样。长连接建立后,可能十几分钟甚至几小时都没有数据流动。如果你沿用普通的 timeout client / timeout server(比如 30 秒),WebSocket 连接会被无端掐断。解决办法是用 timeout tunnel 为隧道类连接设置超时,或者干脆用 timeout client 和 timeout server 配合,为特定后端设置极长的空闲超时。常见做法:
backend websocket_servers timeout tunnel 1h timeout client 1h timeout server 1h server ws-01 10.0.0.31:8080 checktimeout tunnel 的语义是:一旦连接进入隧道模式(比如 WebSocket 升级成功),就不再适用普通的 client/server 超时,而是用 tunnel 超时,默认是 1 小时。如果你不设 timeout tunnel,HAProxy 会继续用 timeout client/server,默认值大概率会在几分钟内让连接断掉。还有一点要注意:WebSocket 这种长连接,负载均衡算法一定要用 leastconn,因为每一条连接都会长期占用一个后端节点的资源,按连接数分配才是合理的。
4.3 排查超时问题的三个切入点
遇到超时类故障,我最常用的排查思路是先分清楚是哪一段超时。很多人的第一反应是看后端日志,但后端日志往往显示请求根本没到,这时去查后端完全是浪费时间。
第一步,先看 HAProxy 的错误日志。HAProxy 的日志里会带着终止状态码,比如 SD(server disconnect)表示后端主动断开,SC(socket connect error)表示连接失败,PT(client timeout)表示客户端超时,S(server timeout)表示后端超时。通过日志定位超时发生在哪一端,比你猜来猜去快得多。
第二步,看运行状态的连接数。echo "show info" 可以看当前连接数、队列情况;echo "show servers state" 可以看每个后端的当前连接数和状态。如果某个后端节点的连接数明显高于其他节点,说明负载均衡策略可能没起效,或某个节点被慢请求拖住了。
第三步,用 tcpdump 抓包确认数据是否真的到了后端。这个手段听起来重,但在超时问题排查里特别有效。比如你怀疑是 timeout client 太短导致客户端断连,抓包能看到客户端发送 RST 的时间点;怀疑是后端不响应,能看到 HAProxy 发出请求后,后端迟迟没有 Ack 或 Response。抓包信息比任何日志都诚实。
4.4 分离流量、分层超时:生产环境更精细的配置法
做了一段时间 HAProxy 运维之后,我越来越倾向把同一个 HAProxy 实例里的流量按业务特征做拆分,而不是一个大 backend 走天下。这个思路可以概括为“分层超时、按域配置”。
具体操作是:frontend 上按域名或路径把流量分到不同的 backend,每个 backend 有自己独立的超时策略和负载均衡算法。比如动态接口流量走 api_servers,超时 30 秒;长轮询流量走 longpoll_servers,超时 60 秒;WebSocket 流量走 websocket_servers,用 tunnel 超时;文件上传走 upload_servers,http-request 超时调大到 60 秒。这样即使某个后端出问题,也只影响对应业务,不会拖累整个入口。
这样设计还有一个好处:新接一个业务时,不需要动全局配置,只要在 frontend 里加一条 ACL 和一个 backend 就行。改动局部化,风险自然就小。团队里其他人看到配置也一目了然,不会出现“为了一个慢接口把全站超时都调大”这种事。
5. 常见问题速查表与实测心得
5.1 超时与负载均衡问题速查表
我在实际运维中整理了下面这张速查表,遇到问题可以先对号入座,能省下不少排查时间。
| 现象 | 可能原因 | 处理办法 |
|---|---|---|
| 接口偶尔 504,后端日志无记录 | timeout server 太短 | 调大对应 backend 的 timeout server |
| 上传大文件失败 | timeout http-request 太短 | 调大 http-request 或针对路径 set-timeout |
| WebSocket 连接几分钟就断 | 未配置 timeout tunnel | 设置 timeout tunnel 和较长的 client/server 超时 |
| 某台后端流量明显偏高 | 算法不适合或 weight 失衡 | 根据场景改用 leastconn 并检查权重 |
| 健康检查频繁误杀节点 | timeout check 太短或健康接口太慢 | 设计轻量健康检查接口,调大 timeout check |
| 后端重启后大量 502 | connect 超时太短 | 调大 timeout connect,配合 rise 预热 |
| 客户端连接堆积,内存上涨 | timeout client 过大 | 适当缩短 client 空闲超时,清理僵尸连接 |
| 改了配置后重启导致连接中断 | 使用了 restart | 改用 reload 热加载 |
5.2 关于超时和负载均衡的几条实操心得
第一个心得:超时配置一定要用业务数据来验证,而不是靠“感觉”。我刚接手一个项目时,timeout server 设的是 30 秒,业务方一直反馈正常。后来压测发现,P99 响应时间在高峰期会飙到 25 秒,离 30 秒只剩 5 秒余量,风险极大。后来我把 timeout server 调到 60 秒,又单独给慢接口设置了 120 秒的动态超时,才真正把故障概率降下来。建议每个季度根据后端接口的 P99/P999 响应时间,重新审视一遍超时配置。
第二个心得:负载均衡算法的选择和业务的生命周期强相关。业务刚上线时,我建议先用 roundrobin,因为它的行为最可预期,方便定位问题。等业务稳定了,再根据实际请求特征切换到 leastconn 或哈希类算法。不要一上来就追求“智能”,复杂度就是故障源。leastconn 虽好,但它依赖连接数这个指标的准确性,如果你的后端是纯短请求且连接数波动极大,leastconn 的效果不一定比 roundrobin 好。
第三个心得:HAProxy 的配置和 nginx 一样,本身就是一种代码资产,要纳入版本管理。我见过太多团队拿着生产环境的 haproxy.cfg 当草稿纸,今天加一条、明天改一个,最后没人说得清楚线上到底跑的是什么配置。我的习惯是:所有变更先进 Git 仓库,通过 CI 做语法检查,再走发布系统 reload。配合 stats socket 的即时调整能力,线上应急时可以先动态改权重、摘除节点,事后再把变更固化到配置文件里。这套流程虽然看起来多几步,但能在半夜出事时救你一条命。