为什么用 HAProxy 而不是 Nginx 来做负载均衡
Nginx 是万能工具:Web 服务器、反向代理、负载均衡,一个 upstream 块就能把流量分到几台后端。那为什么还有大量生产环境专门架一层 HAProxy?答案在于职责分离和协议能力。
Nginx 做七层 HTTP 代理很强,但它对四层 TCP/UDP 流量的处理是后加的,配置别扭;而 HAProxy 从诞生第一天就是为「高可靠四层 + 七层代理」设计的。当你的需求是「把 MySQL 读写、Redis、MQTT、gRPC 这些非 HTTP 流量也做负载均衡,同时还要毫秒级健康检查和优雅摘除」时,HAProxy 往往比 Nginx 更合适。本文用实测配置讲清楚 HAProxy 的核心概念、健康检查机制和各种踩坑点。
一、先把四个核心概念理清:frontend / backend / listen / defaults
HAProxy 的配置文件结构比 Nginx 直观得多,只有四类块:
- global:全局设置,进程数、最大连接、日志目标。只出现一次。
- defaults:后续所有 frontend/backend 的默认值,可以出现多次,每次影响它之后的块。
- frontend:面对客户端的监听入口,绑定 IP:端口,决定把流量交给哪个 backend。
- backend:一组真实的服务器 server 列表,负责调度和健康检查。
- listen:frontend 和 backend 的合体,写简单的场景省事。
一个最小可用的 HTTP 代理配置:
global
maxconn 4096
log /dev/log local0
daemon
defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
option httplog
frontend web_in
bind *:80
default_backend web_servers
backend web_servers
balance roundrobin
server app1 10.0.0.11:8080 check
server app2 10.0.0.12:8080 check
server app3 10.0.0.13:8080 check注意三个 timeout 是必填的,HAProxy 不会给你默认值,漏了直接启动失败。这和 Nginx 的「不写就有默认」哲学相反:HAProxy 要求你把每个决策都显式写出来。
二、健康检查:check 背后的真实机制
server 行末尾的 check 是 HAProxy 最核心的能力。它的默认行为是:每隔 2 秒(inter)对后端发起一次 TCP 连接测试,连续 3 次失败(fall)就把该服务器标记为 down,连续 2 次成功(rise)再标记为 up。
但这只是 TCP 层探测,端口开着就认为健康。很多情况下我们需要应用层探测:
backend web_servers
balance leastconn
option httpchk GET /healthz HTTP/1.1\r\nHost:\ www.example.com
http-check expect status 200
default-server inter 2s fall 3 rise 2
server app1 10.0.0.11:8080 check
server app2 10.0.0.12:8080 checkoption httpchk 让探测变成一次真实的 HTTP 请求,http-check expect status 200 要求返回 200 才算健康。这样后端进程虽然还在监听端口,但内部依赖(数据库连接)挂了、/healthz 返回 500 时,HAProxy 就能自动把它摘掉。
关键坑位:健康检查用的 Host 头默认是 server 行的名字(这里是 app1),很多应用会因为这个 Host 不匹配而返回 400/404,导致所有节点被误判为不健康。必须显式写 Host:\ www.example.com,且用 \r\n 分隔头和请求行,写法不能错。
三、调度算法:roundrobin 不是万能的
HAProxy 提供多种 balance 算法,选错的代价很大:
roundrobin:轮询,适合后端性能均衡、请求耗时接近的场景。注意它动态调整权重,慢节点会被少分流量。leastconn:分给当前连接数最少的节点。适合请求耗时差异大(比如有慢查询)的场景,这是生产环境最常用的算法。source:按客户端 IP 哈希,保证同一用户落到同一后端,用于没有共享 session 的应用。uri:按 URL 路径哈希,适合缓存型后端,让同一 URL 总是命中同一台缓存服务器。
实测建议:默认用 leastconn,比 roundrobin 更抗抖动。只有当后端有本地缓存(要保证同 URL 打同一台)时才用 uri。用 source 要小心背后的 NAT:大量用户从同一个出口 IP 进来,会被全部压到一台后端。
四、四层 vs 七层:mode tcp 才是 HAProxy 的杀手锏
前面都是 mode http。当你要代理 MySQL、Redis、MQTT 这类非 HTTP 协议时,切到 mode tcp:
frontend mysql_in
bind *:3306
mode tcp
default_backend mysql_servers
backend mysql_servers
mode tcp
balance leastconn
option tcp-check
server db1 10.0.1.11:3306 check
server db2 10.0.1.12:3306 checkmode tcp 下 HAProxy 只做字节转发,完全不懂上层协议,因此性能极高、延迟极低。这对 MySQL 从库读负载均衡、Redis 集群前置、游戏服务器等场景非常合适——这些恰恰是 Nginx 做起来最别扭的地方。
但要注意:tcp 模式下没有会话保持的概念,除非用 source 哈希。MySQL 客户端有长连接和事务,如果每个新连接被分到不同后端,事务就会乱。所以 MySQL 场景必须用 balance source 或者干脆指定单台主库。
五、日志:默认什么都看不到,必须手工开
HAProxy 最反直觉的设计是日志默认关闭。什么都不配,出问题你连一行日志都没有。必须在 global 里加 log,在 defaults/frontend/backend 里加 option httplog:
global
log /dev/log local0
log /dev/log local1 notice
defaults
log global
option httploglog global 表示继承 global 的日志目标。还要确认系统 rsyslog 会把 local0 写进文件——很多发行版默认把 local0 丢掉。加一条 /etc/rsyslog.d/49-haproxy.conf:
local0.* -/var/log/haproxy.log
local1.* -/var/log/haproxy.log前面的减号 - 表示异步写(不等待落盘),对高流量代理能显著降低延迟。改完 systemctl restart rsyslog。
六、统计页:不开就是瞎运维
HAProxy 自带的 stats 页面是排查问题的神器,能看到每台后端的状态、连接数、错误率、每秒请求数:
listen stats
bind *:8404
mode http
stats enable
stats uri /haproxy?stats
stats refresh 5s
stats auth admin:StrongPass123
stats admin if TRUEstats admin if TRUE 让你能在页面上手工把某台后端标记为维护状态(MAINT),或者强制恢复。这个端口千万不要暴露到公网,用防火墙限制只允许内网或跳板机访问。stats auth 用的是 HTTP Basic 认证,明文密码在网络上传输,必须配合 HTTPS 或内网才安全。
实测中最常见的一幕:线上报 502,打开 stats 发现某台后端状态是 DOWN,health check 失败次数在涨。没有这个页面,你只能靠猜。
七、HTTPS 卸载与 SNI 多证书
让 HAProxy 终结 TLS,后端走明文 HTTP,可以省掉后端的证书配置:
frontend https_in
bind *:443 ssl crt /etc/haproxy/certs/
mode http
default_backend web_servers
# 按域名分流
acl host_shop hdr(host) -i shop.example.com
use_backend shop_servers if host_shop把多个 .pem 文件(证书 + 私钥拼接在一起)放进 /etc/haproxy/certs/ 目录,HAProxy 会按 SNI 自动选择正确的证书。关键坑:pem 文件的权限要正确,且每个 pem 里的域名必须和客户端请求的 SNI 匹配,否则握手失败。合并证书和私钥的命令:
cat fullchain.pem privkey.pem | tee /etc/haproxy/certs/example.com.pem别忘了 HAProxy 需要能读这些文件,通常是 chmod 600 且属主为 haproxy。
八、优雅重载:别让配置更新掉线
改了配置要生效,直接 systemctl restart haproxy 会断开所有连接。正确做法是 reload:
haproxy -c -f /etc/haproxy/haproxy.cfg # 先校验语法
systemctl reload haproxy # 优雅重载,老连接走完再退reload 的原理是启动新进程接管监听 socket,老进程继续处理存量连接直到超时后退出,实现零丢包。配合 hard-stop-after 30s(在 global 里设置),可以强制老进程最多再活 30 秒,避免它拖太久占着旧代码。
九、连接队列与限流:高并发下的保护
HAProxy 在 frontend 和 backend 都能设连接上限,这是保护后端不被压垮的关键。maxconn 在 frontend 限制该监听口的总并发,超出后新连接会在内核 accept 队列里排队而不是被拒绝:
frontend web_in
bind *:80
maxconn 2000
default_backend web_servers
backend web_servers
# 每台后端最多 100 个并发连接
server app1 10.0.0.11:8080 check maxconn 100
server app2 10.0.0.12:8080 check maxconn 100backend 里的 maxconn 是每台服务器的并发上限,达到后新请求会排队等待其他后端腾出容量。这对保护慢后端特别有效,避免它被瞬间打满后雪崩。注意 queue 的等待也有上限,由 frontend 的 maxconn 和超时共同决定,排太久的请求会被 503。
还要看内核的两个队列参数,它们和 HAProxy 的 maxconn 是两回事:
sysctl net.core.somaxconn
ss -lnt | grep :80 # 看 Send-Q 是 backlog,Recv-Q 堆积说明 accept 跟不上如果 ss -lnt 显示 80 端口的 Recv-Q 持续非零,说明 HAProxy 的 accept 速度跟不上新连接,往往是 worker 数或 maxconn 设小了,而不是后端的问题——这个区分点能帮你避免误判方向。
十、粘性会话与 cookie 插入
后端没有共享 session(比如 PHP 默认的本地文件 session)时,必须保证同一用户的请求落到同一台服务器。HAProxy 可以自动插入并跟踪 cookie,比按 IP 哈希更精准:
backend web_servers
balance roundrobin
cookie SERVERID insert indirect nocache
server app1 10.0.0.11:8080 check cookie s1
server app2 10.0.0.12:8080 check cookie s2cookie SERVERID insert 让 HAProxy 在响应里插入一个名为 SERVERID 的 cookie;indirect 表示对客户端隐藏后端的真实 cookie;nocache 保证带这个 cookie 的响应不会被缓存。server ... cookie s1 是每台后端的标识值。这样后端不需要任何改动,就能实现会话粘性。
坑位:如果后端自己也要设同名 cookie,会冲突;如果后端开启了页面缓存,带 SERVERID 的响应被 CDN 缓存后,别的用户可能被粘到别人的后端上——nocache 就是防这个。用之前先确认后端是否有状态,纯无状态服务别开粘性,开了反而让负载不均匀。
十一、和 Nginx 的分工:别重复造轮子
常见的架构是「HAProxy 在最前面做四层负载均衡和 TLS 卸载,后端放多台 Nginx 做七层路由」。这种分层的好处是:HAProxy 只做连接级别的转发,性能极高、几乎不占资源;复杂的 URL 重写、缓存、PHP 处理交给 Nginx,各司其职。
但也有人反过来,在 HAProxy 后面再套一层 Nginx 做静态资源,然后每台 Nginx 再 proxy_pass 到应用。层数太多会带来两个问题:一是排查链路变长,出故障要逐层看日志;二是每一层 TCP 连接都会增加延迟。经验法则是最多两层代理,再多就考虑用服务网格或者干脆合并。
把这几块拼起来——合理的调度算法、应用层健康检查、tcp 模式覆盖非 HTTP 流量、开日志、开 stats、优雅重载、连接限流、会话粘性——你就得到了一套比 Nginx upstream 更适合做基础设施级负载均衡的方案。工具没有绝对好坏,关键是让 HAProxy 干它擅长的四层/七层代理,让 Nginx 回去干它擅长的 Web 服务,各司其职,运维才不至于被一堆耦合在一起的配置拖垮。