1. 从单机到群集:为什么负载均衡是绕不开的一步
很多刚接触后端部署的朋友,第一次听到“Web群集”这个词,脑子里浮现的画面可能是好几台服务器并排摆着,每台都跑着同样的代码,然后用户请求自动就分流出去了。但真到自己动手的时候,第一个卡住的问题往往不是“怎么搭群集”,而是“请求凭什么知道该找哪台机器”。
这个问题的答案就是负载均衡器。你可以把它想象成一个饭店门口的领位员:客人来了不会自己冲进后厨找空位,而是由领位员根据各桌的占用情况,把人带到合适的桌子前。HAProxy 扮演的就是这个领位员的角色,它站在所有 Web 服务器的前面,接收所有进来的 HTTP 请求,然后按照预设的策略把请求转发给后面某一台真实的 Web 服务器。
那为什么不用 Nginx 做这件事?这是我在实际项目里被问得最多的问题之一。Nginx 确实也能做反向代理和负载均衡,而且很多人更熟悉它。但 HAProxy 有几个特性让它在“纯负载均衡”这个场景下更专注:它的健康检查机制更细致,支持四层(TCP)和七层(HTTP)两种转发模式,会话保持的配置更灵活,而且在高并发场景下的资源占用表现相当稳定。说白了,Nginx 是个多面手,HAProxy 是个专才。如果你的核心诉求就是把流量稳稳当当地分到后端一堆机器上,HAProxy 是更省心的选择。
这篇文章面向的是已经有一台以上 Web 服务器、想让它们协同工作的读者。不管你用的是云主机还是自建机房,只要你能通过内网 IP 互相访问,这套方案就能落地。我会从环境准备开始,一步步把 HAProxy 的安装、配置、后端群集搭建、健康检查、会话保持、日志排查全部串起来,中间穿插我自己踩过的坑和实测有效的调优参数。
2. 动手之前的家底盘点:环境规划与基础准备
2.1 机器角色分配与网络规划
在敲任何命令之前,先把机器的角色和 IP 规划清楚。我见过太多人上来就装软件,结果配到一半发现 IP 写混了,回头改配置改到怀疑人生。下面是我常用的一套最小可用群集规划,三台机器就能跑起来:
| 角色 | 主机名(示例) | 内网 IP(示例) | 作用 |
|---|---|---|---|
| 负载均衡器 | lb-node | 192.168.1.10 | 运行 HAProxy,对外暴露服务 |
| Web 节点 1 | web-node-1 | 192.168.1.11 | 运行 Web 服务(如 Nginx/Apache) |
| Web 节点 2 | web-node-2 | 192.168.1.12 | 运行 Web 服务(如 Nginx/Apache) |
这里有个细节值得展开说:HAProxy 所在的机器最好只跑 HAProxy,不要再跑 Web 服务。原因很简单,如果 HAProxy 和 Web 服务混布,一旦 Web 服务把 CPU 或内存吃满,HAProxy 的转发性能会直接受影响,整个群集的入口就堵住了。我早期为了省机器这么干过,结果一次流量小高峰直接把入口拖垮,后来老老实实做了角色分离。
另外,所有机器的时间要同步。HAProxy 的日志里会记录请求时间,如果各节点时间不一致,排查问题时日志对不上,你会非常痛苦。用chronyd或ntpd把时间同步配好,这是基本功。
2.2 后端 Web 节点的统一化处理
群集里的 Web 节点,内容必须是一致的。否则用户第一次请求看到的是 A 页面的内容,刷新一下被分到另一台机器,看到的是 B 页面,体验就崩了。统一内容有几种常见做法:
- 共享存储挂载:把网站根目录放在 NFS 或分布式文件系统上,所有 Web 节点挂载同一个目录。优点是改一次全生效,缺点是多了一个单点,NFS 挂了所有节点都读不到文件。
- 代码同步工具:用 rsync 定时同步,或者用 Git 在每台机器上拉同一份代码。适合静态内容为主、更新频率不高的场景。
- 容器化部署:每个 Web 节点跑同样的容器镜像,镜像里自带代码。这是目前最干净的做法,但需要额外的容器编排知识。
不管用哪种方式,先确保你直接访问每台 Web 节点的内网 IP 时,看到的内容是一样的。这一步没做扎实,后面负载均衡配好了你也不知道是均衡生效了还是内容本来就一样。
2.3 防火墙与 SELinux 的预处理
HAProxy 默认监听 80 或 443 端口,后端 Web 节点也监听 80。如果你的机器开了防火墙,需要放行这些端口。我一般会在实验阶段先把防火墙规则理清楚,而不是直接关掉防火墙了事——关防火墙在生产环境是绝对不允许的,养成好习惯比图省事重要。
在基于 RHEL 系的系统上,大致需要放行 HTTP 和 HTTPS 服务:
# 查看当前防火墙规则 firewall-cmd --list-all # 永久放行 HTTP 和 HTTPS firewall-cmd --permanent --add-service=http firewall-cmd --permanent --add-service=https firewall-cmd --reloadSELinux 这块,如果你确认不需要它介入,可以设为 permissive 模式观察,但不建议直接 disabled。HAProxy 连接后端端口时,SELinux 有时会拦截,用ausearch或sealert能看到具体的拒绝记录。我个人的做法是保持 enforcing,然后针对性地加策略,这样既安全又不会莫名其妙被拦。
3. HAProxy 的安装与核心配置拆解
3.1 安装方式的选择与实操
HAProxy 的安装有几条路:包管理器直接装、源码编译、容器镜像。对于绝大多数场景,包管理器装是最省事的,版本也够用。以常见的 Linux 发行版为例:
# Debian/Ubuntu 系 apt update apt install -y haproxy # RHEL/CentOS 系 yum install -y haproxy装完之后先别急着改配置,用haproxy -v看一眼版本。不同大版本的配置语法有差异,比如 1.8 和 2.x 在部分指令上就不一样。我建议至少用 2.0 以上的版本,因为 2.x 对多线程的支持更好,配置也更清晰。
源码编译适合需要特定功能模块或者包管理器版本太老的情况。编译前确认装了gcc、make、pcre-devel、openssl-devel这些依赖,然后make TARGET=linux-glibc再make install。编译出来的二进制默认在/usr/local/sbin/haproxy,需要自己写 systemd 单元文件来管理。除非有明确需求,否则我不推荐新手走编译这条路,坑多且没必要。
3.2 配置文件的结构逻辑
HAProxy 的配置文件通常放在/etc/haproxy/haproxy.cfg。这个文件的结构非常清晰,分成几个大块,每块各司其职。理解了这个结构,后面改配置就是往里填内容的事。
global 段:全局设置,影响 HAProxy 进程本身。比如最大连接数、日志目标、运行用户等。这里有个容易忽略的点——maxconn这个参数。它限制的是单个 HAProxy 进程能同时处理的最大连接数。如果设得太小,高并发时新连接会被拒绝;设得太大,又可能耗尽系统文件描述符。我的经验值是先看系统ulimit -n的上限,然后maxconn设为文件描述符上限的 80% 左右,留出余量给其他进程。
defaults 段:默认参数,会被后面的 frontend、backend、listen 段继承。超时设置基本都在这里配。超时这块是重灾区,配不好会出现各种“连接莫名断开”的问题,后面我会专门展开讲。
frontend 段:定义 HAProxy 对外暴露的入口。它负责监听端口、接收请求,然后决定把请求交给哪个 backend。你可以把它理解为“前台接待”。
backend 段:定义后端真实服务器的列表和负载均衡策略。它负责具体的转发和健康检查。可以理解为“后厨团队”。
listen 段:frontend 和 backend 的合体,适合简单场景。但为了配置清晰,我一般分开写 frontend 和 backend,不混用 listen。
3.3 一个可直接套用的基础配置
下面这份配置是我在多个项目里验证过的骨架,你可以直接拿去改 IP 和端口:
global log /dev/log local0 log /dev/log local1 notice chroot /var/lib/haproxy stats socket /run/haproxy/admin.sock mode 660 level admin stats timeout 30s user haproxy group haproxy daemon maxconn 4096 defaults log global mode http option httplog option dontlognull timeout connect 5000ms timeout client 50000ms timeout server 50000ms errorfile 400 /etc/haproxy/errors/400.http errorfile 403 /etc/haproxy/errors/403.http errorfile 408 /etc/haproxy/errors/408.http errorfile 500 /etc/haproxy/errors/500.http errorfile 502 /etc/haproxy/errors/502.http errorfile 503 /etc/haproxy/errors/503.http errorfile 504 /etc/haproxy/errors/504.http frontend http_front bind *:80 default_backend http_back backend http_back balance roundrobin server web1 192.168.1.11:80 check server web2 192.168.1.12:80 check这份配置里,balance roundrobin是轮询策略,请求会依次分给 web1、web2、web1、web2……check表示开启健康检查,HAProxy 会定期探测后端节点是否存活。option httplog让日志记录详细的 HTTP 信息,排查问题时非常有用。
改完配置后,别直接重启,先用haproxy -c -f /etc/haproxy/haproxy.cfg做语法检查。这个命令能提前发现大部分配置错误,避免重启失败导致服务中断。检查通过后再systemctl restart haproxy。
4. 负载均衡策略与健康检查的实战细节
4.1 几种均衡算法的适用场景
HAProxy 支持的均衡算法不少,但常用的就那么几种。选错了算法,群集的效率会打折扣。
roundrobin(轮询):最基础的算法,按顺序轮流分配。适合后端节点性能相近、请求处理时间差不多的场景。它的优点是绝对公平,缺点是没考虑节点当前的实际负载。
leastconn(最少连接):把新请求分给当前连接数最少的节点。适合请求处理时间差异大的场景,比如有的请求秒回,有的请求要跑好几秒。用 leastconn 能避免某个节点堆积大量长请求而其他节点闲着。
source(源地址哈希):根据客户端 IP 做哈希,同一个 IP 的请求总是分到同一台后端。这是实现会话保持的一种方式,但有个坑——如果客户端经过 NAT 网关,大量用户共享同一个出口 IP,那所有请求都会压到一台机器上,均衡就失效了。
uri(URI 哈希):根据请求的 URI 做哈希,同一个 URL 的请求总是分到同一台后端。适合缓存场景,能提高缓存命中率。
我一般默认用 roundrobin,遇到长连接或处理时间差异大的业务换成 leastconn。source 和 uri 只在有明确会话或缓存需求时才用。
4.2 健康检查的机制与参数调优
健康检查是群集高可用的核心。HAProxy 会定期向后端节点发探测请求,如果连续失败几次,就把这个节点标记为 down,不再往它转发流量;等它恢复后,再自动加回来。
默认的check参数已经够用,但如果你想精细控制,可以加这些参数:
server web1 192.168.1.11:80 check inter 2000 rise 2 fall 3inter 2000:每 2000 毫秒(2 秒)检查一次。rise 2:连续成功 2 次才认为节点恢复。fall 3:连续失败 3 次才认为节点宕机。
这几个值的组合决定了故障切换的速度。inter 越小、fall 越小,发现故障越快,但探测请求也越频繁,对后端有额外压力。我的经验是:内网环境 inter 设 2 秒、fall 设 3 次,故障发现时间大约 6 秒,这个速度对大多数业务够用了。如果业务对故障切换极其敏感,可以调到 inter 1 秒、fall 2 次,但要观察后端能不能承受这个探测频率。
还有一个细节:默认的健康检查是 TCP 层面的,只要端口能连上就算健康。但有时候 Web 服务进程还在,端口也开着,实际已经无法处理请求了(比如数据库连不上)。这时候需要 HTTP 层面的健康检查:
option httpchk GET /health http-check expect status 200这会让 HAProxy 发一个 GET 请求到/health路径,只有返回 200 状态码才认为节点健康。你需要在 Web 应用里实现这个健康检查接口,让它真正反映服务的可用状态。
4.3 会话保持的三种实现路径
用户登录后,会话信息通常存在单台服务器的内存或本地文件里。如果下一个请求被分到另一台机器,会话就丢了,用户会被踢回登录页。解决这个问题有三条路:
路径一:源地址哈希。前面提到的balance source,同一个 IP 固定分到同一台后端。配置最简单,但 NAT 场景下会失衡,而且用户换网络(比如从 WiFi 切到 4G)后 IP 变了,会话照样丢。
路径二:Cookie 插入。HAProxy 可以在响应里插入一个 Cookie,标记这台后端,后续请求带着这个 Cookie 就会被分到同一台后端:
backend http_back balance roundrobin cookie SERVERID insert indirect nocache server web1 192.168.1.11:80 check cookie web1 server web2 192.168.1.12:80 check cookie web2这种方式比源地址哈希更可靠,因为 Cookie 跟着用户走,不受 IP 变化影响。indirect表示对客户端隐藏后端信息,nocache表示不缓存这个 Cookie。
路径三:会话共享。把会话数据存到 Redis 或数据库里,所有 Web 节点都从同一个地方读写会话。这是最彻底的方案,Web 节点完全无状态,随便怎么均衡都不影响。缺点是引入了一个外部依赖,Redis 挂了会话就全丢了。
我的建议是:如果应用改造成本低,优先走会话共享;如果不想改代码,用 Cookie 插入;源地址哈希只作为临时方案。
5. 日志、监控与常见故障的排查链路
5.1 让 HAProxy 日志真正可用
HAProxy 默认的日志配置有个坑:它把日志发给本地的 syslog,但很多系统的 rsyslog 默认不接收来自 HAProxy 的日志,导致日志文件里空空如也。你需要确认 rsyslog 配置里开启了 UDP 514 端口的监听,并且在/etc/rsyslog.conf里加了类似这样的规则:
local0.* /var/log/haproxy.log然后在 HAProxy 的 global 段里指定log /dev/log local0。重启两个服务后,/var/log/haproxy.log里就能看到请求日志了。
日志格式方面,option httplog会输出类似这样的内容:
Feb 10 10:23:45 localhost haproxy[1234]: 192.168.1.100:54321 [10/Feb/2025:10:23:45.123] http_front http_back/web1 0/0/1/2/3 200 1234 - - ---- 1/1/0/0/0 0/0 "GET /index.html HTTP/1.1"这里面每个字段都有含义,重点看几个:http_back/web1表示请求被转发到了 web1 节点;200是返回状态码;最后的GET /index.html是请求内容。排查“请求到底去了哪台机器”时,就看这个字段。
5.2 一次 502 错误的完整排查过程
502 Bad Gateway 是 HAProxy 场景下最常见的错误之一。我遇到过好几次,下面还原一次典型的排查链路。
现象:用户访问站点,间歇性出现 502,刷新几次又正常。
第一步,看 HAProxy 日志。日志里出现大量SC--或sH--这样的状态标记。SC表示服务器主动断开连接,sH表示服务器返回了健康检查失败。这说明后端节点有问题。
第二步,看后端节点状态。通过 HAProxy 的统计页面(后面会讲怎么开)看到 web2 节点状态是 DOWN。但直接访问 web2 的 IP 又是通的。这就矛盾了。
第三步,看健康检查配置。发现配置里用了option httpchk GET /health,但 web2 上的应用还没实现/health接口,返回的是 404。HAProxy 认为 404 不是 200,就把节点标记为 DOWN 了。而 web1 恰好实现了这个接口,所以只有 web2 被踢出。
第四步,修复。要么在 web2 上补上/health接口,要么把健康检查改回 TCP 模式。我选择了补接口,因为 HTTP 健康检查更准确。
这个案例的教训是:启用 HTTP 健康检查前,务必确认所有后端节点都实现了对应的检查路径,否则会出现“节点明明是好的却被踢掉”的诡异现象。
5.3 统计页面的开启与日常监控
HAProxy 自带一个统计页面,能直观看到每个后端节点的状态、连接数、流量等。开启方式是在配置里加一个 listen 段:
listen stats bind *:8404 stats enable stats uri /stats stats refresh 10s stats auth admin:yourpassword重启后访问http://负载均衡器IP:8404/stats,输入用户名密码就能看到面板。这个页面我建议只在内部网络开放,不要暴露到公网,因为stats auth的密码是明文配置在文件里的,安全性有限。
面板上重点看几个指标:每个节点的 Status(UP/DOWN)、Sessions(当前连接数)、Bytes In/Out(流量)。如果某个节点的 Sessions 明显高于其他节点,说明均衡策略可能有问题,或者这个节点处理请求特别慢导致连接堆积。
6. 性能调优与高可用延伸
6.1 系统层面的参数调整
HAProxy 跑在高并发场景下,系统默认的一些参数会成为瓶颈。下面这几个是我每次部署都会调的:
# 增大文件描述符上限 echo "haproxy soft nofile 65535" >> /etc/security/limits.conf echo "haproxy hard nofile 65535" >> /etc/security/limits.conf # 内核网络参数 net.ipv4.tcp_tw_reuse = 1 net.ipv4.ip_local_port_range = 1024 65000 net.ipv4.tcp_max_syn_backlog = 8192 net.core.somaxconn = 8192tcp_tw_reuse让 TIME_WAIT 状态的连接可以被快速复用,减少端口耗尽的风险。somaxconn和tcp_max_syn_backlog增大连接队列,避免高并发时新连接被丢弃。这些参数改完后用sysctl -p生效。
6.2 超时参数的取舍逻辑
超时设置是 HAProxy 配置里最容易配错的部分。配得太短,正常的长请求会被切断;配得太长,故障节点会拖住大量连接不释放。
timeout connect:HAProxy 连接后端节点的超时。内网环境设 3 到 5 秒足够。如果后端响应慢,先查后端,不要盲目加大这个值。
timeout client:客户端连接 HAProxy 的超时。这个值要大于你业务里最长的请求处理时间。比如有个报表导出要跑 30 秒,那timeout client至少设 60 秒。
timeout server:HAProxy 等待后端响应的超时。同样要大于后端最长的处理时间。
我的经验是:先观察业务的实际请求耗时分布,取 P99 耗时再乘以 2 作为超时值。不要拍脑袋设一个很大的值,那样故障时连接会堆积。
6.3 从单台 HAProxy 到高可用入口
单台 HAProxy 本身也是个单点。如果它挂了,整个群集就不可访问了。要解决这个问题,需要在前面再加一层高可用机制。常见方案是 keepalived + VIP:两台 HAProxy 组成主备,共用同一个虚拟 IP,主节点挂了备节点自动接管 VIP。
这个方案的配置涉及 keepalived 的 VRRP 协议、优先级设置、健康检查脚本等,展开又是一大篇。这里只提一个关键点:keepalived 检查 HAProxy 是否存活时,不能只检查进程在不在,还要检查 HAProxy 能不能正常转发请求。我见过进程活着但转发功能已经异常的案例,所以健康检查脚本里最好加一个实际的 HTTP 探测。
7. 我在实际部署中攒下的几条经验
配置 HAProxy 这件事,看文档能学会 80%,剩下 20% 全靠踩坑。下面这几条是我在不同项目里反复验证过的。
第一条,改配置永远先做语法检查。haproxy -c -f这个命令花不了两秒钟,但能避免 90% 的重启失败。我早期有过直接重启导致服务中断的经历,后来这个习惯再也没破过。
第二条,后端节点的健康检查路径要统一。前面 502 的案例已经说明了,如果有的节点实现了/health有的没实现,HAProxy 会把没实现的节点全部踢掉,你以为配了群集,实际上只有一台机器在扛。
第三条,日志一定要先调通再上生产。没有日志的 HAProxy 就是个黑盒,出了问题只能靠猜。花十分钟把 rsyslog 配好,后面排查问题能省几个小时。
第四条,统计页面是日常巡检的好帮手。每天花一分钟看一眼各节点的连接数和状态,能提前发现很多隐患,比如某个节点连接数持续偏高、某个节点频繁 UP/DOWN 切换。
第五条,不要忽视会话问题。很多群集搭好之后“看起来能用”,但用户登录后频繁掉线,根源就是会话没保持。上线前一定要用真实的多步操作流程测试,而不是只访问一个静态页面就完事。
这套方案从最小三台机器起步,扩展到十几台后端节点都没问题。核心思路就是:HAProxy 做统一入口,后端节点无状态化,健康检查保可用,日志监控保可查。把这四件事做扎实,Web 群集的稳定性就有底了。