1. 从基础到进阶:Haproxy还能怎么折腾
上篇聊完了Haproxy的基本安装、配置骨架、简单的四七层转发,那篇主要解决的是“怎么让它跑起来”。这篇继续往深挖,把几个真正影响生产稳定性的关键点一次性讲透:高可用怎么搭、性能参数怎么调、ACL路由怎么写不踩坑,以及现在Kubernetes场景下Haproxy ingress到底怎么用才顺手。
搜Haproxy的应该分两类人,一类刚接手一个老项目,里面堆了一堆转发规则,读着费劲;另一类是在设计新系统的入口层,纠结是选Nginx还是Haproxy,或者已经选型定了,但心里没底不知道上线前要调哪些参数。这篇两种需求都照顾一下,所有规则和思路我都按生产环境的标准来写,你照着落地不会出大问题。
说白了,Haproxy的价值不在于“能把请求转过去”,而在于同一个请求进来之后,它能不能稳定、快速、可控地送到后端。这一篇围绕稳定、快速、可控展开。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 方案选型:单点Haproxy不如不部署
2.1 先想清楚Haproxy在你的架构里到底是什么角色
很多人一开始就把Haproxy当成“另一个Nginx”来用,这其实有点浪费。Nginx处理静态文件、反向代理、缓存都顺手,Haproxy的核心场景更聚焦:四层转发、七层路由、负载均衡算法、健康检查、会话保持、流量控制。它不缓存文件,也不自己跑业务代码,就是专职做流量调度。
在实际架构里,Haproxy通常扮演三种角色。第一种是边缘入口的统一接入层,外部流量先到它,再由它分发给后端应用;第二种是内部服务间的网关,比如微服务场景下多个内部API通过Haproxy做路由和负载均衡;第三种是数据库或缓存前置的流量分发器,这个场景往往被忽略,实际上很多MySQL从库集群、Redis集群前面就用Haproxy做读写分离和故障剔除。
想明白角色之后,部署形态就清晰了。如果只是单台Haproxy顶在业务前面,后端应用单点问题解决了,但Haproxy自身挂了业务全断,这在生产里等于没做高可用。所以只要上生产环境,第一件事就是把Haproxy做成双机热备或者多实例集群,不能让它成为新的单点。
2.2 为什么老团队还在坚持用Haproxy而不是All in Nginx
我在不同公司见到过不少架构团队在入口选型上的纠结。Nginx生态丰富、资料多、能做的事也多,但Haproxy在几个硬指标上确实有不可替代的优势。
首先,Haproxy的资源占用极其克制。一个小规格ECS上可以轻松扛住几万并发连接,内存和CPU的消耗比同配置的Nginx要低不少。有人做过对比,同样干四层转发,Haproxy的单连接内存开销比Nginx低一个量级。公司里如果有一批性能较差的旧机器不想浪费,Haproxy是个很合适的入口方案。
其次,Haproxy的配置模型比Nginx更贴近“负载均衡”这个语义。upstream、backend、listen、frontend这几种段位逻辑清晰,用namespace隔离多个vhost场景时比Nginx的server层更好管理。尤其是做TCP四层转发,Nginx从1.9才开始支持stream模块,而Haproxy天生就是干这个的。
第三,Haproxy的统计页面和socket控制台是运维利器。线上出问题时,想知道当前有没有慢请求、后端有没有异常、连接池是否打满,一条命令或者打开统计页就能看到,不需要额外装监控。这种“所见即所得”的排障体验,长期维护过系统的人都懂。
3. 高可用部署:keepalived配合Haproxy双主或主备
3.1 VIP漂移的套路与配置细节
Haproxy本身不提供VIP能力,所以业界标准做法是用keepalived做VIP漂移。架构上一般两台机器跑同一个配置的Haproxy,keepalived负责在两台机器之间管理同一个虚拟IP(VRRP协议里的VIP),正常情况下VIP落在主节点上,主节点故障后VIP漂移到备节点。
一个非常关键的认知是:keepalived负责的是“机器级”故障切换,不是“进程级”。如果Haproxy进程还活着但已经假死,keepalived自身不会感知到。所以必须在keepalived里配置chk_haproxy脚本,定期检测Haproxy进程状态,一旦进程异常就降低本机的VRRP优先级或者直接杀掉keepalived,触发VIP切换。
下面这套是我在线上用了很久的配置模板,注释里写清楚每行的含义。
主节点的keepalived.conf:
bash复制global_defs {
router_id lb01
# 脚本检测haproxy进程是否存活
vrrp_script chk_haproxy {
script "/etc/keepalived/check_haproxy.sh"
interval 2
weight -20
rise 2
fall 2
}
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 120
advert_int 1
nopreempt
# 认证信息在企业内网通常用双字母/数字组合
authentication {
auth_type PASS
auth_pass 1234
}
virtual_ipaddress {
192.168.10.100/24 dev eth0
}
track_script {
chk_haproxy
}
}
check_haproxy.sh脚本内容:
bash复制#!/bin/bash
if [ $(pgrep -c haproxy) -eq 0 ]; then
exit 1
else
exit 0
fi
这段脚本本身不复杂,但实际线上踩坑往往出在细节上。比如nopreempt这个参数,如果不加,主节点恢复后VIP会立刻抢回来,切换期间所有长连接全部断开,对业务影响很大。加了nopreempt之后,主节点恢复后保持备节点状态,VIP不来回飘,连接稳定性好很多。但注意,nopreempt只在state MASTER的节点上生效,如果两个节点都是BACKUP,它不工作。
还有一点容易忽略的是VRRP通告间隔。默认1秒,内网链路正常情况下够用,但如果网络抖动严重,可以适当调大advert_int并同步调整超时倍数。太小会导致频繁误切,太大则故障恢复时间拉长。
3.2 双主部署与连接闪断的取舍
双主模式和主备模式最大的区别是,两台机器各自有独立的VIP,流量同时从两个入口进入,互为主备。这种方案对资源利用率友好,两台机器都在干活,不像主备模式备机常年空闲。代价是配置复杂度上升,而且业务方需要感知到“入口有多个IP”,前端的LB设备或者DNS解析要做分流。
实操上,双主模式通常这样落地:LB01上跑VIP1和VIP2,VRRP实例分别标记为master/bakcup,LB02则反过来;Haproxy配置里同样跑两组backend,但监听地址分别绑不同的VIP。这样任何一个Haproxy故障,它负责的那个VIP会漂到对端,流量不中断。
不过工程上都清楚,VIP漂移只是解决了路由层的可达性,已经建立的TCP长连接该断还是断。比如客户端到Haproxy的隧道连接、数据库连接池里的空闲连接,VIP切走之后这些连接是无法跨节点“续传”的。想减少这种闪断影响,一个是客户端侧尽量用短连接或者带重试的调用方式,另一个是在Haproxy上合理设置timeout client和timeout server,连接空闲超时不要设成无限大,这样即使发生漂移,重建连接的代价也可以接受。
我在实际项目里见过一种更稳的组合:外层用云平台的SLB挂两个后端Haproxy节点,SLB做健康检查,后端Haproxy挂掉后SLB自然把流量只转发给存活节点。这种方案不需要VIP漂移,切换由云平台完成,运维心智负担小很多。自建机房里没有SLB,才更需要keepalived这套方案兜底。
4. 性能调优:连接数、超时、内核参数一个都不能少
4.1 先调内核再调Haproxy,顺序反了一切白搭
很多人调Haproxy性能上来就改maxconn,改完之后发现连接数上不去,误以为Haproxy不行。其实瓶颈大概率在操作系统层。Haproxy的单进程模型虽然高效,但一台机器的可用文件描述符上限、TCP连接的本地端口范围、半连接队列长度这些内核参数会直接限制它的表现。
以下是我常用的内核参数调整清单,直接在/etc/sysctl.conf里配好,然后sysctl -p生效:
bash复制# 文件句柄上限,按单机预估连接数来定
fs.file-max = 1000000
# 允许的TIME_WAIT连接复用,高频短连接场景尤其重要
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
# 作为LB时本机发起大量后端连接,可以分配的临时端口范围要放宽
net.ipv4.ip_local_port_range = 1024 65000
# 每个socket的读写缓冲,大流量高并发场景建议调大
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
# 半连接队列长度,防瞬时连接洪峰打满
net.ipv4.tcp_max_syn_backlog = 65536
net.core.somaxconn = 65536
# 允许更多处于TIME_WAIT的连接被快速回收或复用
net.ipv4.tcp_max_tw_buckets = 2000000
注意几个关键点。tcp_tw_reuse和tcp_tw_recycle不同,tcp_tw_recycle在NAT场景下会引发严重问题,我一般不启用,只开tcp_tw_reuse。fs.file-max是系统级限制,还需要用ulimit -n 1048576提高进程级限制,两者都要改,缺一不可。
讲到这说句经验之谈:凡是高并发的入口服务,上线前第一件事不是改业务配置,而是把操作系统层面的“地基”打牢。以前我带团队排查过一个Haproxy连接数上不去的问题,最后原因就是单个进程的nofile限制,默认1024,直接把并发摁住了,Haproxy日志里全是Resource temporarily unavailable。
4.2 Haproxy自身参数的正确姿势
内核调完,再回来看Haproxy配置。maxconn是最核心的参数,它同时受内核fd限制和内存影响。一个长连接大约消耗几十KB内存,建议按“预估峰值连接数x 2”来预留。下面的配置可以在全局段里设置:
bash复制global
maxconn 100000
nbthread 4
cpu-map auto 1/1-4 0-3
tune.ssl.default-dh-param 2048
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11
并发线程数的设置,在新版本Haproxy里用nbthread而不是老旧的nbproc。多进程模式下各个进程各自独立,配置session状态的stick-table不好共享;多线程模式则在一个进程内跑多个线程,共享内存状态,管理成本更小。
超时时间是我见过配置出问题最多的地方。HTTP短连接场景,timeout http-request要设得短一点,比如5秒,防止慢速攻击拖死后端;timeout client和timeout server用于长连接或WebSocket,根据业务自己定;timeout connect尤其重要,后端一旦开始假死,这个超时时间决定了Haproxy多久能发现并切换,我一般设3~5秒。
bash复制defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
timeout http-keep-alive 2s
timeout queue 5s
timeout http-request 5s
retries 2
option redispatch
option http-keep-alive
option redispatch这个选项值得多说一句。当某个后端节点被标记为down,而客户端还有请求在队列里排队时,如果开启了redispatch,Haproxy可以把这些请求分给其他正常节点,避免用户长时间等待。对无状态服务来说强烈建议开,对有状态服务则需要配合会话保持谨慎使用。
retries设置的是Haproxy与后端建立连接失败后重试的次数。不宜太多,否则分布式系统间的调用延迟会被成倍放大。2次是比较均衡的配置。
4.3 会话保持:不是所有业务都需要,但需要时别走弯路
会话保持常见的实现方式有两种:一种是通过stick-table按来源IP或者Cookie做会话绑定,另一种是通过cookie SERVERID insert indirect nocache,让Haproxy插入会话Cookie来实现。
IP哈希有个显而易见的问题,就是多出口NAT环境下,大量用户从同一个出口IP访问时,请求会被固定到同一台后端,造成负载不均。Cookie方式相对更精细,适合HTTP场景;纯TCP四层转发则只能靠IP哈希或者stick-table基于源地址做。
bash复制backend web_servers
balance roundrobin
cookie SERVERID insert indirect nocache
server web1 10.0.0.11:8080 weight 10 check cookie web1
server web2 10.0.0.12:8080 weight 10 check cookie web2
这个配置实现的效果是:后端返回响应时,Haproxy自动插入一个名为SERVERID的Cookie,值为服务器标识(web1或web2);后续同一客户端的请求带着这个Cookie,Haproxy就会将其转发到同一台后端。indirect表示后端自身产生的同名Cookie优先,nocache防止代理服务器或浏览器缓存这个会话Cookie。
注意一个容易被忽略的问题:后端发生滚动发布或者扩容缩容时,如果Cookie标识发生了变化,用户可能被重新分配。所以在发布窗口内最好让后端保持稳定的server name,变更时用disable server先摘流量,处理完再enable server,把会话影响降到最低。
5. 路由玩出花:ACL与backend组合的精髓
5.1 基于路径、域名、SNI的多条件路由
ACL是Haproxy最灵活的部分,也是新手最容易搞出“规则互相覆盖”问题的重灾区。我建议把ACL当成一种“标签匹配器”,先定义标签,再基于标签组合决定路由去向。
看下面这个例子,一个前端入口同时承接了API和Web两类流量,又按域名把不同项目隔离开:
bash复制frontend web_gateway
bind *:80
bind *:443 ssl crt /etc/haproxy/certs/site.pem
mode http
# 不同域名
acl is_api hdr(host) -i api.example.com
acl is_admin hdr(host) -i admin.example.com
acl is_static hdr(host) -i static.example.com
# 路径前缀匹配只对API处理
acl path_api path_beg /v1/ /v2/
acl path_health path -i /healthz
# 基于SSL SNI,需要前面bind了ssl
acl is_app_sni ssl_fc_sni -i app.example.com
# 使用后端的定义
use_backend api_servers if is_api path_api
use_backend admin_servers if is_admin
use_backend static_servers if is_static or is_app_sni
default_backend web_servers
这里有几个容易踩坑的细节。
第一,use_backend的匹配顺序是从上到下第一个匹配成功就生效。所以需要把最精确的规则放前面,比如is_api path_api的组合要放在is_admin之前,否则is_admin的请求如果也满足path_api条件,会先被API规则截胡。
第二,ACL匹配逻辑里逗号表示“或”,空格表示“与”。很多初学者在这个地方搞混,写path_beg /v1/ /v2/这行时,实际语义是path_beg /v1/ 或者 /v2/,不是“既要v1又要v2”。写成多行其实更容易理解和维护。
第三,如果同一个后端要被多个域名引用,最好用变量做后端标识,不要每个站点都复制一段server列表,否则后期改端口或者加机器时要改十几个地方。
5.2 安全拦截与请求改写
Haproxy做边缘入口时,顺手做一层基础安全拦截很合适。比如阻止特定的User-Agent,限制单IP的请求速率,或者在响应头里统一加上安全头。
bash复制frontend web_gateway
# 屏蔽恶意扫描UA
acl is_bad_ua hdr_substr(user-agent) -i sqlmap nikto nmap
http-request deny deny_status 403 if is_bad_ua
# 限制每个源IP每秒请求数,用stick-table实现
stick-table type ip size 100k expire 1m store http_req_rate(10s)
# 这里需要移到http-request track-sc0
http-request track-sc0 src
http-request deny deny_status 429 if { sc0_http_req_rate(10s) gt 100 }
# 设置安全响应头
http-response set-header X-Content-Type-Options nosniff
http-response set-header X-Frame-Options SAMEORIGIN
http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains"
stick-table的限制逻辑要用http-request track-sc0 src先把源IP记录到表里,后续再用sc0_http_req_rate(10s)检查这个IP过去10秒内的请求频率。这里sc0中的0对应绑定到该stick-table的跟踪ID。生产上一般配合gpc0计数做封禁,但简单场景下直接限速就够了,防止自己把正常用户误杀。
切到HTTPS时务必统一在Haproxy这一层把HTTP强制跳转HTTPS做了,后端应用不用再自己处理跳转逻辑。配置方法是:
bash复制frontend http_redirect
bind *:80
http-request redirect scheme https unless { ssl_fc }
这里ssl_fc表示连接已经是SSL终结的,只要不是HTTPS连接就全部301到HTTPS。注意redirect使用的状态码,通常配301,但如果业务方后续要改域名,建议临时使用302,避免浏览器缓存导致切域名后用户一直跳到旧地址。
6. Kubernetes场景下的Haproxy Ingress
6.1 为什么有人放着Nginx Ingress不用,选择Haproxy Ingress
现在K8s集群里最常见的Ingress Controller是Nginx Ingress,但Haproxy Ingress Controller在特定场景下的优势非常明显。
首先是四层能力。Nginx Ingress默认处理HTTP/HTTPS七层流量,虽然后来也支持了TCP和UDP,但配置相对繁琐。Haproxy Ingress在设计上对四层和七层的支持是一体化的,你可以在同一个入口上既跑HTTP路由,又透传特定TCP端口到后端服务,这对一些遗留系统上云很友好。
其次是性能。Haproxy Ingress Controller底层还是Haproxy那一套,资源占用低,单实例承载的连接数高。在同等规格Pod下,它能支撑的QPS和并发连接数通常优于Nginx Ingress。
第三是配置语义更贴近业务。Haproxy Ingress通过annotations直接暴露很多调优参数,比如超时时间、会话保持、熔断阈值等,不需要像Nginx Ingress那样写一堆ConfigMap片段去映射Nginx配置。
6.2 Haproxy Ingress Controller部署与常用配置
部署Haproxy Ingress Controller最省心的方式是Helm。helm仓库地址加一下,一条命令就起一个实例:
bash复制helm repo add haproxytech https://haproxytech.github.io/helm-charts
helm repo update
helm install haproxy-ingress haproxytech/kubernetes-ingress \
--namespace ingress-haproxy --create-namespace \
--set controller.kind=Deployment \
--set controller.replicas=2 \
--set controller.service.type=LoadBalancer
controller.kind可以选DaemonSet或者Deployment。如果集群规模不大,每个节点都起一个Ingress Pod会浪费资源,用Deployment跑两个副本就够了。多副本部署时记得把controller.replicas从2起步,并且打开POD的反亲和策略,让两个副本尽量调度到不同节点。
Ingress资源的写法与Nginx Ingress大体一致,但Haproxy的annotations是一套独立体系。几个常见的annotations配置如下。
yaml复制apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo-app
namespace: default
annotations:
# 会话保持,开启后同一客户端IP会绑定到同一后端Pod
haproxy.org/load-balance: "roundrobin"
haproxy.org/cookie-persistence: "SERVERID"
# 后端连接超时
haproxy.org/server-timeout: "30s"
haproxy.org/client-timeout: "30s"
# 每IP限流
haproxy.org/rate-limit-requests: "1000"
haproxy.org/rate-limit-period: "10s"
# 后端健康检查路径
haproxy.org/health-check-uri: "/healthz"
haproxy.org/health-check-port: "8080"
spec:
ingressClassName: haproxy
rules:
- host: api.demo.com
http:
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: demo-api
port:
number: 8080
注意ingressClassName要写成haproxy,它和Haproxy Ingress Controller启动参数里的--ingress.class对应。如果集群里同时装了Nginx Ingress和Haproxy Ingress,不写这个字段的Ingress资源会被两边同时接管,到时候路由规则就乱了。
生产环境用Haproxy Ingress时还有一个很实用的经验:Ingress资源里尽量只写路由规则,不要把SSL证书的Secret往资源里堆。证书统一用Ingress Controller的默认证书机制管理,或者挂载到Controller所在命名空间,这样证书轮换不需要改动Ingress资源本身。
6.3 Haproxy Ingress的监控与运维要点
Haproxy Ingress Controller的监控机制比自建Haproxy稍微复杂一点。Controller的Pod里通常包含两个容器:haproxy和haproxy-ingress,监控指标的暴露端口在--metrics-port指定的端口上,默认是9105。
采集方式很简单,Prometheus加一条scrape配置:
yaml复制- job_name: 'haproxy-ingress'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_label_app]
regex: 'haproxy-ingress'
action: keep
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
regex: 'true'
action: keep
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
target_label: __metrics_path__
regex: '(.+)'
- source_labels: [__address__]
action: replace
regex: '([^:]+)(?::\d+)?'
replacement: '$1:9105'
target_label: __address__
Haproxy exports的metrics体系里,我最常盯的是下面几个:
haproxy_backend_http_requests_total看每个backend的历史请求总量,分析分流是否均衡;haproxy_server_http_responses_total按响应码分类,看到5xx突增就说明后端可能有问题;haproxy_backend_current_queue是当前队列深度,这个指标一上涨就说明后端Pod处理不过来了,得赶紧扩容;haproxy_backend_servers_state是后端节点的在线状态,在告警规则里配上阈值能在服务不可用之前就报警。
运维上有几个非常实用的命令,Habproxy进程活着但流量异常的排查利器:
bash复制# 查看backend当前队列与连接情况
echo "show info" | socat stdio /var/run/haproxy.sock
# 查看所有backend的状态,删线或down一目了然
echo "show servers state" | socat stdio /var/run/haproxy.sock
# 查看某条会话的详细信息,用于排障链路
echo "show sess" | socat stdio /var/run/haproxy.sock
# 查看最近错误,比如后端连接失败
echo "show errors" | socat stdio /var/run/haproxy.sock
socat工具没有预装时需要yum install socat或apt install socat,但这个工具在诊断时非常值得。记得确认Haproxy的配置里开了stats socket /var/run/haproxy.sock level admin,否则只能看不能改。
7. 常见问题与排查技巧实录
7.1 后端健康检查与502/503的迷思
现象:后端应用明明没挂,Haproxy却一直报502,或者标记后端为DOWN。
排查思路:第一步打开Haproxy的统计页,看后端的具体状态。DOWN的原因主要看健康检查类型与后端实际健康检查端口是否一致。默认的HTTP健康检查是OPTIONS /,但有些后端应用对OPTIONS请求处理得不好;MySQL、Redis这类非HTTP服务则需要改用option tcp-check做应用层探测。
实操修正:
bash复制backend mysql_backend
option tcp-check
tcp-check connect
tcp-check send "SELECT 1\r\n"
tcp-check expect string "1"
server db1 10.0.0.21:3306 check inter 5s fall 3 rise 2
后端如果是Spring Boot这类Java应用,健康检查路径建议用/actuator/health,返回的JSON里status字段为UP才表示存活。可以靠HTTP检查的http-check expect匹配响应体:
bash复制http-check expect string "UP"
用HTTP检查时注意检查间隔inter与失败阈值fall的配合。默认3秒检查一次、3次失败算DOWN,但如果后端GC时间超过10秒,健康检查就会误判。我建议对Java后端把inter调整到5秒,fall设为5次,避免瞬时卡顿导致节点被摘除。
504的排查方向:502通常出现在“连接建立”阶段,比如后端拒绝连接、健康检查失败;504是“连接已建立但长时间没响应”。遇到504先查后端应用的线程池是否被占满,再看Haproxy的超时设置是否过长,最后用show sess看具体卡在哪一步等待。
7.2 maxconn与timeout queue的连锁反应
现象:后端响应正常,但用户访问时经常转圈很久才返回。
排查思路:这种转圈通常是请求进入haproxy的等待队列。maxconn设置得太小会让请求进入队列,timeout queue默认值也决定了能排多久。
Haproxy在没有可用连接时会自动把请求放入队列,等后端释放连接资源。这个机制本身没问题,但如果maxconn设得保守,正常流量下也会一直堆队列。我见过一个案例,后端8个Pod,Haproxy的maxconn却只有2000,高峰期单Pod连接数超过300,请求全在排队,用户体验极差。
排查方法很简单,看统计页里的Qcur(当前队列)和Qmax(最大队列),如果长期大于0,就说明连接数存在瓶颈。调高maxconn后要同时检查后端Pod的连接上限和内核参数,确保整条链路都能扛得住。
7.3 会话保持引发的负载不均衡
现象:某个后端节点的流量明显高于其他节点,但CPU和内存都很健康。
排查思路:大概率是会话保持策略的问题。Cookie方式下,一旦某些热门终端或网关被绑定到同一台后端,这个节点就会越来越热。
对应策略有两个方向。一是后端服务本已经无状态化,就可以关闭会话保持,让负载均衡算法纯轮询;二是后端确实有状态,可以把balance算法改为leastconn,让新连接尽量分配给连接数最少的节点,配合会话保持使用,均衡效果会好很多。
bash复制backend stateful_servers
balance leastconn
cookie SERVERID insert indirect nocache
server s1 10.0.0.31:8080 check cookie s1
server s2 10.0.0.32:8080 check cookie s2
注意leastconn在短连接场景下效果不明显,因为连接总是很快关闭;它更适合长连接场景,比如WebSocket、数据库代理、推送服务。
7.4 排障速查表
| 问题 | 排查命令/配置 | 常见原因与处理思路 |
|---|---|---|
| 健康检查误判DOWN | 查看stats页当前state | 检查健康检查类型与端口、调整inter和fall |
| 502 Bad Gateway | show serv + 查看后端日志 |
后端端口未监听、后端拒绝连接、haproxy与后端的网络不通 |
| 504 Gateway Timeout | show sess + 后端线程栈 |
后端线程池打满、SQL慢查询、timeout server过短 |
| 请求堆积排队 | stats页的Qcur持续高 |
maxconn过大或后端容量不足,根据连接数扩容 |
| 会话不均衡 | 查看每个server的连接数 | 调整balance策略或检查Cookie标识是否被缓存 |
| 配置reload后不生效 | haproxy -c -f + reload |
配置校验不合格不会加载,查语法时注意缩进 |
8. 最后再分享几个实战里验证过的小要点
Haproxy的坑基本踩过一遍,后面用起来就顺了。靠近末尾说几个我实际维护中发现的小细节,每一个都曾经影响过线上稳定性,希望能帮你在后面的运维中少走弯路。
一是重新加载配置时用reload而不是restart。Haproxy支持无缝reload,新进程接管监听端口,旧进程处理完存量连接后自然退出。用restart会导致存量连接全部断开,生产上这是要尽量避免的。
二是开启option allbackups要谨慎。这个配置会让流量分发给所有健康节点,包括被标记为backup的节点。在灾备场景下不希望备用节点承接流量时,千万不要开这个选项。
三是TCP四层模式下,Haproxy默认不解析HTTP协议。如果想在四层转发的同时拿客户端真实IP,需要在后端配置option forwardfor,但纯TCP模式下这个是无效的,需要自己在haproxy配置里加上accept-proxy或依赖Proxy Protocol将客户端IP传给后端。常见做法是Haproxy与后端之间启用Proxy Protocol,后端服务支持的话强烈推荐这种方式,真实IP就不会丢。
最后提一个调优思路:Haproxy的配置调整是逐步逼近的过程,不要一次性把maxconn、超时、健康检查全改到位。每次只改一个变量,观察一两天再动下一个,出了问题也容易定位。线上没有银弹,稳定运行的背后都是细心和耐心。
