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)

  1. 配置主机名并互相绑定:
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
  1. 配置静态 IP
  2. 关闭 iptables/firewalld 和 SELinux
  3. 时间同步
  4. 配置 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. 常见排错与注意事项

  1. 配置文件语法检查:修改配置后建议先用 haproxy -c -f /etc/haproxy/haproxy.cfg 检查语法,再 reload。
  2. reload 与 restart 的区别:systemctl reload haproxy 为平滑重载,不断开已有连接;restart 会断开所有连接。
  3. 健康检查文件必须存在:使用 option httpchk 时,后端服务器必须存在检查文件,否则会被标记为 DOWN 并从调度池中摘除。
  4. HTTP/1.0 版本限制:Haproxy 1.5 的 option httpchk 需使用 HTTP/1.0,不支持 HTTP/1.1。
  5. X-Forwarded-For:后端 Web 日志要记录客户端真实 IP,需同时配置 Haproxy 的 option forwardfor 和后端 Web 服务器的日志格式。
  6. Cookie 保持只对浏览器有效:curl/elinks 等工具不支持 Cookie,测试时需用浏览器。
  7. stick table 过期时间:expire 设置要合理,过小会导致频繁重新调度,过大会在节点故障时影响切换。
  8. mode http vs tcp:mode http 支持七层调度(ACL、Cookie、stick table 等);mode tcp 为四层转发,性能更高但无法做内容判断。
最后修改:2026 年 09 月 20 日
如果觉得我的文章对你有用,请随意赞赏