HAProxy 负载均衡
本文系统讲解 Haproxy 负载均衡:软件特性与定位、安装部署、
haproxy.cfg配置文件结构(global/defaults/frontend/backend/listen)、调度算法、ACL 七层规则、健康检查、状态监控页、日志配置(rsyslog + X-Forwarded-For)、三种会话保持方式(source/cookie/stick table)、动静分离实战,以及与 LVS/Nginx 的对比。
1. Haproxy 简介
Haproxy 是一款开源的、支持四层和七层的负载均衡软件,提供反向代理、会话保持、高并发处理等能力。
1.1 与 LVS、Nginx 的对比
| 特性 | LVS | Haproxy | Nginx |
|---|---|---|---|
| 工作层次 | 四层 | 四层/七层 | 四层/七层 |
| 特点 | 稳定、效率高 | 七层调度能力强,会话保持方式丰富 | 七层调度,也有新模块扩展调度功能 |
| 七层内容分发/过滤 | 不支持 | 支持 | 支持,但弱于 Haproxy |
| 会话保持 | 不原生支持(需 sh/persistent) | 多种方式 | 支持 |
| 抗并发能力 | 最强 | 次之 | 再次 |
| 应用范围 | 最广 | 较广 | 较广 |
| 网络复杂度 | 复杂 | 较简单 | 较简单 |
| 负载均衡算法 | 最多 | 较多 | 较少 |
Squid、Nginx cache 可做缓存加速;开源负载均衡常见方案为 Nginx、Haproxy、LVS。
2. 实验环境
2.1 架构说明
客户端(宿主机)192.168.1.3
|
外网 | 192.168.1.31
Haproxy
内网 | 192.168.224.10
|
+-----+-----+
| |
web1 web2
192.168.224.11 192.168.224.12
Haproxy 使用两个网段模拟内外网,实际生产也可以只用一个网卡(只有内网网卡),公网 IP 配置在前端即可。实验中使用两个内网网段是为了防止桥接网络 IP 冲突。
2.2 实验前准备(CentOS 7.3)
- 配置主机名并互相绑定:
hostnamectl set-hostname --static haproxy.com
vim /etc/hosts
192.168.224.10 haproxy.com
192.168.224.11 client1.com
192.168.224.12 client2.com
- 配置静态 IP
- 关闭 iptables/firewalld 和 SELinux
- 时间同步
- 配置 yum 源(Haproxy 服务器需配置 163 源或使用自带网络源)
3. 安装与基础配置
3.1 后台 Web 服务器准备
在 Web1 和 Web2 上分别安装 httpd 并设置不同主页内容以便测试验证。
Web1:
yum install httpd httpd-devel -y
systemctl start httpd
systemctl enable httpd
echo web1 > /var/www/html/index.html
Web2:
yum install httpd* -y
systemctl start httpd
systemctl enable httpd
echo web2 > /var/www/html/index.html
客户端准备:安装 firefox 和 curl/elinks 即可。
3.2 安装 Haproxy
yum install haproxy -y
相关文件:
| 文件 | 说明 |
|---|---|
/etc/haproxy/haproxy.cfg |
主配置文件 |
/usr/share/doc/haproxy-1.5.18/haproxy-en.txt |
参数文档 |
3.3 配置文件结构
haproxy.cfg 由以下配置段组成:
| 配置段 | 说明 |
|---|---|
global |
全局配置参数,主要配置服务用户、PID、socket、进程、chroot 等 |
defaults |
负载调度相关的全局默认配置(调度模式、超时时间、调度算法、健康检查等),对后面的 listen、frontend、backend 都生效 |
frontend |
处理前端接收的请求,可在其中配置 ACL 进行七层调度,指定转发到对应的 backend |
backend |
最终调度的真实服务器(Real Server)相关配置 |
listen |
frontend 和 backend 的综合体,可同时定义监听和后端 |
3.4 基础配置示例
查看去掉注释后的默认配置:
grep -v '#' /etc/haproxy/haproxy.cfg
编辑配置文件:
vim /etc/haproxy/haproxy.cfg
global
log 127.0.0.1 local2
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 4000
user haproxy
group haproxy
daemon
stats socket /var/lib/haproxy/stats
defaults
mode http # 支持7层负载均衡;改成 tcp 则只支持4层
log global
option httplog
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8
option redispatch
retries 3
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 1m
timeout server 1m
timeout http-keep-alive 10s
timeout check 10s
listen 192.168.1.31 *:80
balance roundrobin
server client1.com 192.168.224.11:80
server client2.com 192.168.224.12:80
global 和 defaults 段基本保持默认,优化参数需参考文档和实际生产环境调整。
global 段常用参数
| 参数 | 说明 |
|---|---|
log <address> <facility> |
日志输出地址和设备(如 local2) |
chroot <path> |
改变工作目录并监禁进程,提升安全性 |
pidfile <path> |
PID 文件路径 |
maxconn <n> |
每个进程的最大并发连接数 |
user / group |
运行 Haproxy 的用户和组 |
daemon |
以守护进程方式运行 |
stats socket <path> |
状态统计 socket 路径 |
defaults 段常用参数
| 参数 | 说明 |
|---|---|
mode http\|tcp |
工作模式:http 为七层,tcp 为四层 |
log global |
日志使用 global 段的配置 |
option httplog |
记录 HTTP 详细日志 |
option dontlognull |
不记录空连接日志 |
option forwardfor |
添加 X-Forwarded-For 头,传递客户端真实 IP |
option redispatch |
连接失败后重新分发到其他服务器 |
retries <n> |
连接重试次数 |
timeout http-request |
HTTP 请求超时时间 |
timeout queue |
请求在队列中的等待超时 |
timeout connect |
连接后端服务器的超时 |
timeout client |
客户端空闲超时 |
timeout server |
后端服务器空闲超时 |
timeout http-keep-alive |
keep-alive 超时 |
timeout check |
健康检查超时 |
使用 frontend + backend 替代 listen
将上述 listen 段替换为以下 frontend 和 backend,效果相同:
frontend 192.168.1.31 *:80
default_backend webs
backend webs
balance roundrobin
server client1.com 192.168.224.11:80
server client2.com 192.168.224.12:80
3.5 启动服务
systemctl start haproxy
systemctl enable haproxy
3.6 客户端测试
curl 192.168.1.31
结果:web1 和 web2 的内容轮询出现(rr 轮询)。
4. 调度算法
Haproxy 使用 balance 指令指定调度算法:
| 算法 | 说明 |
|---|---|
roundrobin |
轮询,按顺序依次分配请求,支持动态权重 |
static-rr |
基于权重的静态轮询,与 roundrobin 类似但不支持运行时动态调整 |
leastconn |
最少连接,将请求分配给活动连接数最少的服务器,适合长连接场景 |
source |
源地址哈希,同一客户端 IP 的请求分配到同一服务器(类似 Nginx 的 ip_hash 或 LVS 的 sh) |
uri |
对 URI 进行哈希,同一 URI 的请求分配到同一服务器(适用于缓存场景) |
url_param <param> |
根据 URL 中指定参数的值进行哈希 |
hdr(<name>) |
根据 HTTP 请求头中指定字段的值进行哈希 |
rdp-cookie |
根据 RDP cookie 进行哈希(用于远程桌面负载均衡) |
5. Haproxy 状态监控页
状态页面显示 Haproxy 自身的运行状态和后端各 Web 服务器的统计信息(不是后台 Web 的显示内容),列出后端服务器是为了统计它们的状态。
5.1 在 listen 段配置 stats
listen 192.168.1.31 *:80
stats uri /haproxy-stats
stats realm Haproxy\ statistics
stats auth admin:123456
stats hide-version
balance roundrobin
server client1.com 192.168.224.11:80
server client2.com 192.168.224.12:80
| 参数 | 说明 |
|---|---|
stats uri /haproxy-stats |
状态页访问路径 |
stats realm Haproxy\ statistics |
登录提示信息(realm),空格需转义 |
stats auth user:pass |
登录用户名和密码 |
stats hide-version |
隐藏 Haproxy 版本号 |
重载配置:
systemctl reload haproxy.service
客户端访问:
http://192.168.1.31/haproxy-stats
5.2 在 frontend + backend 中配置 stats
frontend 192.168.1.31 *:80
default_backend servers
backend servers
stats uri /haproxy-stats
stats realm Haproxy\ statistics
stats auth li:li123
stats hide-version
balance roundrobin
server client1.com 192.168.224.11:80
server client2.com 192.168.224.12:80
6. 日志配置
6.1 配置 rsyslog 接收 Haproxy 日志
Haproxy 的日志通过 syslog 发送到 127.0.0.1 的 local2 设备。虽然日志发送到本地,但仍需按远程日志方式配置 rsyslog 监听 UDP 514 端口。
vim /etc/rsyslog.conf
打开以下两行的注释(启用 UDP 514 端口接收日志):
$ModLoad imudp
$UDPServerRun 514
在 $UDPServerRun 514 之后添加:
local2.* /var/log/haproxy.log
local2与 haproxy.cfg 中log 127.0.0.1 local2的配置对应,表示 local2 设备的所有级别日志都记录到/var/log/haproxy.log。
重启 rsyslog:
systemctl restart rsyslog.service
查看日志:
tail -f /var/log/haproxy.log
/var/log/haproxy.log默认不存在,当有用户访问网站时日志文件会自动生成并开始输出。
6.2 Haproxy 日志工作原理小结
在 /etc/haproxy/haproxy.cfg 的 global 段:
log 127.0.0.1 local2
表示 Haproxy 的日志由 local2 设备记录,通过 127.0.0.1:514(UDP)发送给 rsyslog。rsyslog 接收到后按配置写入 /var/log/haproxy.log。客户端访问后用 tailf /var/log/haproxy.log 查看即可。
7. 健康检查
7.1 配置健康检查
健康检查的原理:在后端 Web 服务器的指定路径放置一个文件,如果文件存在则认为服务器健康,否则认为 Web 服务不可用。
listen 192.168.1.31 *:80
stats uri /haproxy-stats
stats realm Haproxy\ statistics
stats auth abc:123
stats hide-version
balance roundrobin
option forwardfor
option httpchk HEAD /check.txt HTTP/1.0
server client1.com 192.168.224.11:80 check inter 2000 rise 2 fall 5
server client2.com 192.168.224.12:80 check inter 2000 rise 2 fall 5
| 参数 | 说明 |
|---|---|
option httpchk HEAD /check.txt HTTP/1.0 |
使用 HTTP HEAD 方法检查 /check.txt,使用 HTTP/1.0 协议(Haproxy 1.5 版本健康检查不支持 HTTP/1.1) |
check |
对该 server 启用健康检查 |
inter 2000 |
检查间隔 2000 毫秒(2 秒) |
rise 2 |
连续 2 次检查成功则标记服务器为可用(UP) |
fall 5 |
连续 5 次检查失败则标记服务器为不可用(DOWN) |
option forwardfor |
向后端转发客户端真实 IP(通过 X-Forwarded-For 头) |
7.2 创建健康检查文件
在 Web1 和 Web2 上分别创建:
touch /var/www/html/check.txt
重载 Haproxy:
systemctl reload haproxy
验证服务正常:
curl 192.168.224.10
7.3 验证健康检查
尝试删除某台 Web 上的 check.txt 文件,然后访问 Haproxy VIP:
# 在某台 Web 上删除检查文件
rm -f /var/www/html/check.txt
# 在客户端访问
curl 192.168.1.31
结果:有 check.txt 文件的后端服务器可以被调度,没有的则不会被调度。
8. 后端日志处理
8.1 问题一:健康检查日志过多
后端 Web 每 2 秒会收到一次健康检查请求,在 access_log 中产生大量 check.txt 日志。
方法一:定时脚本清理
vim rm_head_log.sh
#!/bin/bash
sed -i '/check.txt\ HTTP\/1.0/d' /var/log/httpd/access_log
kill -HUP $(cat /var/run/httpd/httpd.pid)
sed -i配合末尾的d实现删除匹配特定字符串的行。
sed -i直接修改access_log后,httpd 进程可能无法持续写入日志文件,需要用kill -HUP让 httpd 重新打开日志文件。该命令可以在不停止服务的情况下动态更新配置/重新打开日志。
设置定时任务定期执行脚本即可。
方法二:配置 Apache 不记录健康检查日志
vim /etc/httpd/conf/httpd.conf
搜索 CustomLog,修改为:
SetEnvIf Request_URI "^/check\.txt$" dontlog
CustomLog logs/access_log combined env=!dontlog
重启后端 Web:
systemctl restart httpd
8.2 问题二:后端日志记录的是 Haproxy 的 IP 而非客户端真实 IP
后端 Apache 的 access_log 中,客户端访问日志显示的是 Haproxy 的内网 IP,而非实际客户端 IP。
方法一:直接使用前端 Haproxy 的日志
Haproxy 自身日志中已记录客户端真实 IP。
方法二:修改 Apache 日志格式,使用 X-Forwarded-For
确保 Haproxy 配置了 option forwardfor(已在健康检查配置中添加),然后修改 Apache:
vim /etc/httpd/conf/httpd.conf
将原来的 LogFormat combined 替换为:
LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b " combined
CustomLog logs/access_log combined
%{X-Forwarded-For}i表示记录 HTTP 请求头中X-Forwarded-For字段的值,即 Haproxy 转发的客户端真实 IP。
9. 会话保持
9.1 方式一:source 算法
类似 Nginx 的 ip_hash 或 LVS 的 sh 算法,根据客户端源 IP 进行哈希分配,同一客户端 IP 的请求始终转发到同一后端服务器。
listen 192.168.1.31 *:80
stats uri /haproxy-stats
stats realm Haproxy\ statistics
stats auth li:li123
stats hide-version
balance source
option forwardfor
option httpchk HEAD /check.txt HTTP/1.0
server client1.com 192.168.224.11:80 check inter 2000 rise 2 fall 5
server client2.com 192.168.224.12:80 check inter 2000 rise 2 fall 5
systemctl reload haproxy
测试结果:同一客户端的请求始终转发到同一台 Web 服务器(亲缘性保持)。
9.2 方式二:Cookie 会话保持
通过 Haproxy 向客户端写入 Cookie 实现会话保持,对浏览器有效,对 curl/elinks 等不支持 Cookie 的工具无效。
listen 192.168.1.31 *:80
stats uri /haproxy-stats
stats realm Haproxy\ statistics
stats auth li:li123
stats hide-version
balance roundrobin
option forwardfor
option httpchk HEAD /check.txt HTTP/1.0
cookie web_cookie insert nocache
server client1.com 192.168.224.11:80 cookie web1 check inter 2000 rise 2 fall 5
server client2.com 192.168.224.12:80 cookie web2 check inter 2000 rise 2 fall 5
| 参数 | 说明 |
|---|---|
cookie web_cookie insert nocache |
web_cookie 为 Cookie 名称;insert 表示 Haproxy 主动插入 Cookie;当客户端与 Haproxy 之间存在缓存时,建议配合 nocache 使用 |
cookie web1 / cookie web2 |
各后端服务器的 Cookie 标识,不能相同 |
systemctl reload haproxy
测试结果:使用浏览器访问可以保持亲缘性会话,但使用 curl 无法保持(curl 不支持 Cookie/Session)。
9.3 方式三:stick table 会话保持
stick table 是 Haproxy 一个非常优秀的特性。表中存储 stickiness 记录,记录了客户端与后端服务器 1:1 对应的引用关系。通过这种关系,Haproxy 可以将客户端请求引导到之前为它服务过的后端服务器,实现会话保持(会话粘性/stickiness)。
stick table 使用 key/value 方式映射客户端和后端服务器:
- key:客户端标识符,可以是客户端源 IP(50 字节)、Cookie 或从报文中过滤出的字符串。
- value:后端服务器标识符。
优势:
- 每条 stickiness 记录占用空间很小(平均最小 50 字节,最大 166 字节,由是否记录额外统计数据决定),即使在非常繁忙的环境下多节点间推送也不会产生压力瓶颈和网络阻塞。
- 不像 session 复制(copy)那样数据量大、容易造成网络阻塞。session 复制在各应用服务器之间进行,服务器数量多时容易导致网络阻塞。
- Haproxy 重启时,新旧进程会建立 TCP 连接,旧进程将 stick table 推送给新进程(本地复制),新进程不会丢失粘性信息;其他节点只需推送重启期间新增的记录即可保持同步。
- 配置多节点组后,新增或更新的 stickiness 记录会通过 TCP 连接自动推送到组内其他节点,即使某节点故障,其他节点也能按粘性映射引导客户端。
配置示例:
listen 192.168.1.31 *:80
stick-table type ip size 1m expire 1m
stick on src
stats uri /haproxy-stats
stats realm Haproxy\ statistics
stats auth li:li123
stats hide-version
balance roundrobin
option forwardfor
option httpchk HEAD /check.txt HTTP/1.0
server client1.com 192.168.224.11:80 check inter 2000 rise 2 fall 5
server client2.com 192.168.224.12:80 check inter 2000 rise 2 fall 5
| 参数 | 说明 |
|---|---|
stick-table type ip size 1m expire 1m |
定义 stick table,key 类型为 IP,最多存储 100 万条记录,1 分钟无请求则记录过期 |
stick on src |
以客户端源 IP 作为 key 进行粘贴 |
systemctl reload haproxy
验证:无论使用 curl 还是浏览器,都可以保持亲缘性会话。
10. ACL 七层调度实战
10.1 ACL 规则基础
ACL(Access Control List)用于在 frontend 段中匹配请求的各种特征,然后根据匹配结果决定将请求转发到哪个 backend。
常用 ACL 匹配指令:
| 指令 | 说明 |
|---|---|
path_end <后缀> |
匹配 URL 路径的后缀(如 .html、.png) |
path_beg <前缀> |
匹配 URL 路径的前缀(如 /static、/images) |
hdr_beg(host) <值> |
匹配 HTTP 请求头中 Host 的开头 |
src <IP> |
匹配客户端源 IP |
use_backend <名称> if <ACL> |
如果 ACL 匹配则使用指定 backend |
default_backend <名称> |
默认使用的 backend |
block if <ACL> |
匹配 ACL 时阻止请求 |
10.2 实例一:动静分离
将静态文件请求转发到一台后端,动态请求转发到另一台后端。
frontend 192.168.1.31 *:80
# acl invalid_src src 192.168.1.3
# block if invalid_src
acl url_static path_end .html .png .jpg .css .js
use_backend static if url_static
default_backend dynamic
backend static
balance roundrobin
server client1.com 192.168.224.11:80 check inter 2000 rise 2 fall 5
backend dynamic
balance roundrobin
server client2.com 192.168.224.12:80 check inter 2000 rise 2 fall 5
systemctl restart haproxy
测试结果:
.html、.png、.jpg、.css、.js等静态文件请求转发到 224.11(Web1)。- 其他类型请求转发到 224.12(Web2)。
测试方法:在 Web 服务器上创建 .txt 类型文件测试是否转发到 224.12。即使请求不存在的文件,Haproxy 仍会转发到后端 Web。通过查看后端 Web 日志确认转发目标:
tail -f /var/log/httpd/access_log
若需拒绝某 IP 访问,可将注释中的
invalid_src和block if invalid_src打开。
10.3 实例二:基于域名和路径的网站切分
frontend 192.168.1.31 *:80
acl url_static path_end .html .png .jpg .css .js
acl url_static path_beg /static /images /img
acl host_static hdr_beg(host) -i img. video. download.
acl host_www hdr_beg(host) -i www
use_backend static if host_static or url_static
use_backend dynamic if host_www
backend static
balance roundrobin
server client1.com 192.168.224.11:80 check inter 2000 rise 2 fall 5
backend dynamic
balance roundrobin
server client2.com 192.168.224.12:80 check inter 2000 rise 2 fall 5
在客户端编辑 hosts 文件:
vim /etc/hosts
192.168.1.31 www.abc.com
192.168.1.31 img.abc.com
测试结果:
| 命令 | 转发到 | 原因 |
|---|---|---|
curl http://www.abc.com |
Web2(224.12) | Host 以 www 开头,匹配 host_www |
curl http://www.abc.com/sta/abc.abc |
Web2(224.12) | 路径不以 /static 等开头,文件后缀也非静态类型 |
curl http://192.168.1.31/a.html |
Web1(224.11) | URL 以 .html 结尾,匹配 url_static |
curl http://img.abc.com/static/abc.abc |
Web1(224.11) | Host 以 img. 开头匹配 host_static,且路径以 /static 开头匹配 url_static |
重点:如果 URL 同时命中多条 ACL 规则,文件后缀匹配的优先级最高。例如
http://www.abc.com/static/abc.abc中,虽然路径以/static开头,但/static/abc.abc的文件后缀不在静态后缀列表中,而 Host 以 www 开头匹配动态,因此转发到 Web2。而http://www.abc.com/static/a.html因.html后缀匹配,会转发到 Web1。
11. 常见排错与注意事项
- 配置文件语法检查:修改配置后建议先用
haproxy -c -f /etc/haproxy/haproxy.cfg检查语法,再 reload。 - reload 与 restart 的区别:
systemctl reload haproxy为平滑重载,不断开已有连接;restart会断开所有连接。 - 健康检查文件必须存在:使用
option httpchk时,后端服务器必须存在检查文件,否则会被标记为 DOWN 并从调度池中摘除。 - HTTP/1.0 版本限制:Haproxy 1.5 的
option httpchk需使用HTTP/1.0,不支持HTTP/1.1。 - X-Forwarded-For:后端 Web 日志要记录客户端真实 IP,需同时配置 Haproxy 的
option forwardfor和后端 Web 服务器的日志格式。 - Cookie 保持只对浏览器有效:curl/elinks 等工具不支持 Cookie,测试时需用浏览器。
- stick table 过期时间:
expire设置要合理,过小会导致频繁重新调度,过大会在节点故障时影响切换。 - mode http vs tcp:
mode http支持七层调度(ACL、Cookie、stick table 等);mode tcp为四层转发,性能更高但无法做内容判断。
此处评论已关闭