目录
- 一、HAProxy 入门:它到底是什么
- 二、实验环境与网络拓扑
- 三、配置文件结构详解
- 四、配置七层负载均衡
- 五、远程日志采集
- 六、socat 热更新:不停机动态调整
- 七、状态页(Web 监控面板)
- 八、客户端真实 IP 透传
- 九、负载均衡算法详解
- 十、基于 Cookie 的会话保持
- 十一、自定义错误页面
- 十二、ACL 访问控制
- 十三、四层负载实战:MariaDB 数据库集群
- 十四、全站 HTTPS 加密
一、HAProxy 入门:它到底是什么
1.1 什么是负载均衡
先用一个比喻理解负载均衡:
一家餐厅生意火爆,门口排长队,老板于是招了 3 个服务员。可客人进门只认"这家店",不会认服务员。这时候店里需要一位大堂经理:客人进门,经理按"谁空就派给谁"的原则,把客人分给 3 个服务员中的一个。
这里的映射关系是:
| 餐厅角色 | 负载均衡术语 | 说明 |
|---|---|---|
| 店门口招牌 | VIP / 对外地址 | 客户端只认这个地址 |
| 大堂经理 | 负载均衡器(LB) | 负责接收所有请求并分发 |
| 3 个服务员 | Real Server(后端服务器) | 真正干活、响应请求的机器 |
负载均衡的核心价值有四点:
- 提升吞吐量 ------ 多台机器同时干活,总处理能力翻倍;
- 高可用 ------ 某台后端挂了,自动把请求转发给存活的机器,业务不中断;
- 横向扩展 ------ 流量再涨,加机器即可,前端地址不用变;
- 统一入口 ------ 客户端只面对一个地址,架构对使用者透明。
1.2 HAProxy 是什么
HAProxy(High Availability Proxy) 是一个免费、开源、基于 C 语言编写 的 TCP / HTTP 反向代理与负载均衡软件,单机可以支撑十万级并发连接,是很多大厂(GitHub、Stack Overflow 等)都在用的老牌工具。
它的特点:
- 工作在用户态(普通进程),不像 LVS 需要改内核;
- 支持七层(HTTP)和四层(TCP)两种模式,既能负载 Web,也能负载数据库、Redis;
- 自带健康检查,能自动摘掉坏掉的后端;
- 配置灵活,支持 ACL 访问控制、SSL 卸载、会话保持等高级特性;
- 支持热更新(socat 在线调整权重/上下线),不需要重启进程。
1.3 HAProxy 与 LVS、Nginx 的对比
| 对比项 | LVS | HAProxy | Nginx |
|---|---|---|---|
| 工作层级 | 四层(内核) | 四层 + 七层 | 七层为主 |
| 性能 | 最高(内核转发) | 高 | 高 |
| 健康检查 | 较简单 | 非常丰富 | 较简单 |
| 配置灵活性 | 一般 | 高(ACL 等) | 高 |
| 会话保持 | 较弱 | 强(Cookie 等) | 一般 |
| 适用场景 | 超大流量、四层转发 | 七层应用分发、四层数据库/Redis | Web 服务、静态资源、反向代理 |
1.4 七层负载 vs 四层负载
| 四层(L4 / TCP) | 七层(L7 / HTTP) | |
|---|---|---|
| 看的东西 | 只认 IP + 端口 | 能识别 HTTP 协议内容(URL、Header、Cookie、域名) |
| 转发方式 | 直接把 TCP 流量转给后端 | 读懂 HTTP 请求再决定发给谁 |
| 配置关键字 | mode tcp |
mode http |
| 典型场景 | MySQL、Redis、游戏服务 | Web 网站、API 网关、按域名/URL 分流 |
| 优点 | 性能高、协议无关 | 智能,能按内容路由、做 SSL 卸载 |
| 缺点 | 看不到业务内容 | 只能处理 HTTP 类协议 |
二、实验环境与网络拓扑
2.1 主机规划
本次实验用 VMware Workstation 搭了一台 HAProxy + 两台后端(RHEL 系虚拟机),后端先装好 Nginx/Apache 提供一个测试页面。
| 功能 | IP | 说明 |
|---|---|---|
| 客户端 | 本机 Windows(192.168.17.1) | 用浏览器 / curl 测试 |
| HAProxy 调度器 | eth0:192.168.17.100(对外) eth1:193.168.0.100(对内) | 双网卡,前接客户端,后接后端 |
| RS1(后端 1) | eth0:193.168.0.10 | 网关指向 193.168.0.100 |
| RS2(后端 2) | eth0:193.168.0.20 | 网关指向 193.168.0.100 |
2.2 拓扑图
客户端(Windows 本机)
|
| 192.168.17.0/24 网段(NAT 模式,模拟外网/客户端)
|
+----------------------------+
| HAProxy 调度器 |
| eth0 : 192.168.17.100 | <- 对外服务口,客户端访问这个 IP
| eth1 : 193.168.0.100 | <- 对内通信口,连接后端(也是后端的网关)
+-------------+---------------+
|
| 193.168.0.0/24 网段(仅主机模式,内网)
+-------------+---------------+
| |
+----+---------+ +------+--------+
| RS1 | | RS2 |
| .10 | | .20 |
| 网关:.100 | | 网关:.100 |
+--------------+ +---------------+
注意一个关键点:RS1 / RS2 的默认网关都指向 193.168.0.100(HAProxy 的 eth1)。这样后端回包会原路返回给 HAProxy,由 HAProxy 转发给客户端------这和 LVS 的 NAT 模式思路一致。
2.3 HAProxy 双网卡配置
先给 HAProxy 的 eth1(内网口)新建一个网络连接配置,再把 eth0(外网口)改为静态 IP:
bash
[root@localhost li]# nmcli con add type ethernet con-name eth1 ifname eth1 ipv4.method manual ipv4.addresses 193.168.0.100/24
连接 "eth1" (c5b0f679-781b-4c3a-9f4b-746c923bcf0b) 已成功添加。
[root@localhost li]# nmcli con up eth1
连接已成功激活(D-Bus 活动路径:/org/freedesktop/NetworkManager/ActiveConnection/4)
[root@localhost li]# nmcli con mod eth0 ipv4.method manual ipv4.addresses 192.168.17.100/24
[root@localhost li]# nmcli con up eth0
连接已成功激活(D-Bus 活动路径:/org/freedesktop/NetworkManager/ActiveConnection/5)
逐条解释:
| 命令 | 作用 |
|---|---|
nmcli con add type ethernet con-name eth1 ifname eth1 ... |
新建 eth1 的连接配置,con-name 是连接名,ifname 是实际网卡名 |
ipv4.method manual |
关闭 DHCP,改为手动指定 IP |
ipv4.addresses 193.168.0.100/24 |
指定内网 IP 和掩码 |
nmcli con up eth1 |
激活该连接,让配置立即生效 |
nmcli con mod eth0 ... |
修改 eth0 现有连接 |
2.4 RS1 配置
bash
nmcli con mod eth0 ipv4.method manual ipv4.addresses 193.168.0.10/24 ipv4.gateway 193.168.0.100
[root@localhost li]# nmcli con up eth0
连接已成功激活(D-Bus 活动路径:/org/freedesktop/NetworkManager/ActiveConnection/3)
2.5 RS2 配置
bash
[root@localhost li]# nmcli con mod eth0 ipv4.method manual ipv4.addresses 193.168.0.20/24 ipv4.gateway 193.168.0.100
[root@localhost li]# nmcli con up eth0
连接已成功激活(D-Bus 活动路径:/org/freedesktop/NetworkManager/ActiveConnection/3)
2.6 安装并启动 HAProxy
在 HAProxy 调度器上安装并开机自启:
bash
[root@localhost ~]# dnf install haproxy -y
[root@localhost ~]# systemctl enable --now haproxy.service
| 命令 | 作用 |
|---|---|
dnf install haproxy -y |
安装 HAProxy 软件包(RHEL 8+ 用 dnf,旧版用 yum) |
systemctl enable --now haproxy.service |
enable 设置开机自启,--now 表示立即启动服务 |
三、配置文件结构详解
HAProxy 的主配置文件是 /etc/haproxy/haproxy.cfg 。整个配置文件由几个配置段组成:
haproxy.cfg 整体结构
┌─────────────────────────────────────────────┐
│ global 全局配置:进程级的运行参数 │
│ ─────────────────────────────────────────── │
│ defaults 默认配置:给所有实例套用的默认值 │
│ ─────────────────────────────────────────── │
│ frontend 前端:监听端口,接收请求,定义规则 │
│ backend 后端:定义一组真实服务器 │
│ ─────────────────────────────────────────── │
│ listen 监听:前端+后端写在一起的简写方式 │
│ (一个 listen 同时扮演 frontend + backend) │
└─────────────────────────────────────────────┘
3.1 global 段详解
作用: 设置 HAProxy 进程本身的运行参数(用户、文件、并发、CPU 绑定等),是整个配置最先被读取的一段。
ini
global
log 193.168.0.10 local2 #定义全局的syslog服务;日志服务器需要开启udp协议,最多可以定义两个
chroot /var/lib/haproxy #锁定运行目录
pidfile /var/run/haproxy.pid #指定pid文件
maxconn 4000 #指定连接最大数
user haproxy #指定haproxy的运行用户
group haproxy #指定haproxy的运行组
daemon #指定haproxy以守护进程方式运行
stats socket /var/lib/haproxy/stats #指定haproxy套接字文件
nbproc 2 #指定haproxy的work进程数量,默认1个
cpu-map 1 0 #指定第一个work绑定第一个cpu核心
cpu-map 2 1 #指定第二个work绑定第二个cpu核心
nbthread 2 #指定haproxy的线程数量,默认每个进程一个线程,此参数与nbproc互斥
maxsslconn 10000 #每个haproxy进程ssl最大连接数,用于haproxy配置了证书场景下
maxsslrate 1000 #每秒SSL握手速率
maxconnrate 1000 #全协议每秒新建TCP连接速率
maxconn 10000 #进程所有TCP总并发连接上限
| 参数 | 含义 | 原理补充 / 为什么需要 |
|---|---|---|
log 193.168.0.10 local2 |
把日志通过 UDP 发送到日志服务器(193.168.0.10)的 local2 设施 | HAProxy 本身不写日志文件,靠 syslog 协议外发。local2 是 syslog 的"设施"分类,日志服务器据此区分条目来源 |
chroot /var/lib/haproxy |
锁定运行目录:进程只能访问该目录下的文件 | 安全机制 |
pidfile |
记录进程 PID 的文件路径 | 方便管理进程、热更新时定位 |
user / group haproxy |
以普通用户 haproxy 运行 |
安全机制:不 root 运行,权限最小化 |
daemon |
后台守护进程方式运行 | 不占用终端,像其它系统服务一样常驻 |
stats socket |
监听一个 Unix 套接字,供管理工具(socat)连入 | 没有它 socat 没法连接 |
nbproc 2 + cpu-map |
开 2 个工作进程,并分别绑定到 CPU 0 和 CPU 1 | 进程 → CPU 核绑定,减少进程切换,提升性能;适合多核 CPU |
nbthread 2 |
每个进程开 2 个线程 | 与 nbproc 互斥,二选一使用 |
maxsslconn |
SSL 最大连接数 | 只有在 HAProxy 配置了证书时才需要 |
maxconnrate |
每秒新建 TCP 连接速率上限 | 防止突发流量打爆进程 |
3.2 defaults 段详解
作用: 给所有 frontend / backend / listen 提供一套默认值 ,后面每个实例没写的参数都会继承这里的值。省得每个实例重复写一遍。
ini
defaults
mode http #haproxy实例使用的连接协议
log global #指定日志地址和记录日志条目的syslog/rsyslog日志设备
option httplog #日志记录选项,httplog表示与http会话相关的各种属性
option dontlognull #dontlognull表示不记录空会话连接日志
option http-server-close #等待客户端完整http请求时间,此处为10s
option forwardfor except 127.0.0.0/8 #透传客户端真实ip至后端
option redispatch #当server id对应服务器挂掉后,强制定向到其他健康服务器,重新派发
option http-keep-alive #开启与客户端的会话保持
retries 3 #连接后端服务器失败次数
timeout http-request 10s #等待客户端请求完全接受和处理的最长时间
timeout queue 1m #设置删除连接和客户端收到503或服务不可用等提示信息前的等待时间
timeout connect 10s #设置等待服务器连接成功的时间
timeout client 1m #设置允许客户端处于非活动状态,既不发数据也不收数据的时间
timeout server 1m #设置服务器超时时间,即允许服务器处于既不接也不发数据的非活动时间
timeout http-keep-alive 10s #session会话保持超时时间,此时间段内会转发到相同的后端服务器
timeout check 10s #指定后端服务器的健康检查超时时间
maxconn 3000 #最大承受的并发连接
| 参数 | 含义 | 一句话记住 |
|---|---|---|
mode http |
当前实例工作在七层 HTTP 模式 | 想处理数据库就改成 mode tcp |
option httplog |
日志记录 HTTP 会话属性(方法、URL、状态码等) | 排错必须开,否则日志没内容 |
option dontlognull |
不记录"空连接"(连上啥也没干就断开)的日志 | 防止恶意扫描刷爆日志 |
option http-server-close |
客户端和 HAProxy 之间保持长连接,HAProxy 与后端之间用完就关 | 节省后端连接资源 |
option forwardfor |
在发给后端的请求头里追加 X-Forwarded-For 字段,透传客户端真实 IP | 第 8 章主角,except 127.0.0.0/8 表示本机地址不追加 |
option redispatch |
后端挂了时,把这次请求重新分发给其它健康服务器 | 保证可用性 |
retries 3 |
连接后端失败最多重试 3 次 | 防止单次抖动误伤 |
timeout connect |
等待与后端建立 TCP 连接的最长时间 | 后端没响应就放弃,别傻等 |
timeout client / server |
客户端 / 后端无活动多久视为超时 | 闲置连接自动清理 |
timeout http-request |
等待客户端把整个 HTTP 请求发完的时间 | 防止慢速攻击挂起连接 |
maxconn |
本实例最大并发连接数 | 超出的请求排队或返回 503 |
3.3 server 指令详解
server 是 backend / listen 里声明一台后端服务器的关键字。它的完整语法是:
ini
server <名称> <IP:端口> [参数...]
后面的方括号参数很常用,重点讲 健康检查 相关的一组:
ini
check #对指定real进行健康检查,如果不加此设置,默认不进行健康检查
addr <IP> #可指定的健康状态检测ip,可以是专门的数据网段,减少业务网络流量
port <num> #指定的健康状态检测端口
inter <num> #健康状态检查间隔时间,默认2000ms
fall <num> #后端服务器从线上转为线下的检查的连续失效次数,默认为3
rise <num> #后端服务器从线下恢复线上的检查的连续有效次数,默认为2
weight <weight> #默认为1,最大值为256,0(状态为蓝色)表示不参与负载均衡,但仍接受持久连接
backup #将后端服务器标记为备份状态,只有所有非备份主机down时提供服务
disable #将后端服务器标记为不可用状态,即维护状态,除了持久模式
redir prefix http://xxx.xxx.xxx/ #将临时请求重定向到其他url,只适用于http模式
maxconn <maxconn>
| 参数 | 含义 | 补充 |
|---|---|---|
check |
开启健康检查 | ⚠️ 不写 check 的话 HAProxy 默认永远认为这台机器是好的,从不主动探测 |
inter <时间> |
每隔多久检查一次 | 默认 2000ms;写 inter 2s 就是每 2 秒探测一次 |
fall <次数> |
连续失败多少次判定为下线 | 默认 3;防止一次网络抖动就误判 |
rise <次数> |
连续成功多少次判定为上线 | 默认 2;防止后端刚启动还没就绪就被放流量 |
weight <权重> |
负载权重 | 默认 1,最大 256;权重越大分到的请求越多 |
backup |
标记为备份服务器 | 平时不干活,只有所有正常服务器全挂时才顶上 |
disable |
标记为维护状态 | 手动摘除,常用于排障 |
maxconn |
限制该服务器最大连接数 | 防止某台弱机器被压垮 |
健康检查的完整逻辑是一个状态机 :
UP ↔ DOWN,中间靠inter / fall / rise三个参数控制跳转速度。
UP 状态 ──连续失败 fall 次──▶ DOWN 状态 DOWN 状态 ──连续成功 rise 次──▶ UP 状态
下面看一个完整的 server 示例:
ini
listen webcluster
bind *:80
mode http
balance roundrobin
server web1 193.168.0.10:80 weight 3 check inter 2s fall 3 rise 3 #健康检测间隔2s一次,连续失败3次视为下线,连续成功2次视为上线,权重为3
server web2 193.168.0.20:80 weight 1 check inter 2s fall 3 rise 3 #健康检测间隔2s一次,连续失败3次视为下线,连续成功2次视为上线,权重为1
| 行 | 含义 |
|---|---|
listen webcluster |
定义一个名为 webcluster 的监听实例 |
bind *:80 |
监听所有网卡的 80 端口(* 表示任意 IP) |
mode http |
七层 HTTP 模式 |
balance roundrobin |
负载算法用加权轮询(第 9 章详解) |
server web1 ... weight 3 |
web1 权重 3,每 2 秒健康检查一次,连续失败 3 次下线、连续成功 3 次上线 |
server web2 ... weight 1 |
web2 权重 1,其余同 web1 |
权重 3 : 1 意味着:大约每 4 个请求,3 个分给 web1,1 个分给 web2。
测试效果 (客户端访问,健康检查正常,两台后端均 UP):

四、配置七层负载均衡(HTTP)
七层负载是 HAProxy 最常见的用法。有两种等价写法:frontend + backend 和 listen。
4.1 方式一:frontend + backend
这种写法把"入口"和"服务器列表"拆成两段,适合一个入口对应多组后端(按域名 / URL 分流)的场景。
bash
[root@localhost ~]# vim /etc/haproxy/haproxy.cfg
frontend webcluster
bind *:80
mode http
use_backend web-80
backend web-80
balance roundrobin
server web1 193.168.0.10:80 check
server web2 193.168.0.20:80 check
[root@localhost ~]# systemctl restart haproxy.service
[root@localhost ~]# systemctl stop firewalld
| 行 | 含义 |
|---|---|
frontend webcluster |
定义一个前端,名叫 webcluster(名字随便起) |
bind *:80 |
监听所有网卡的 80 端口,接收客户端请求 |
use_backend web-80 |
把所有请求转发给名为 web-80 的后端 |
backend web-80 |
定义一个后端组,名叫 web-80 |
balance roundrobin |
轮询算法 |
server web1 / web2 ... check |
声明两台后端,check 开启健康检查 |
这条拓扑里的对应关系:
客户端 ──▶ frontend webcluster(:80)──use_backend──▶ backend web-80 │ ├──▶ web1 (193.168.0.10:80) └──▶ web2 (193.168.0.20:80)

重启服务后,用 netstat 确认 80 端口已被 HAProxy 监听:
bash
[root@localhost ~]# netstat -antlupe | grep haproxy
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 0 67253 5425/haproxy
udp 0 0 0.0.0.0:45331 0.0.0.0:* 985 67261 5425/haproxy
udp 0 0 0.0.0.0:55129 0.0.0.0:* 985 67259 5425/haproxy
看到 tcp 0.0.0.0:80 ... LISTEN 就说明 HAProxy 已在 80 端口对外服务了。(
4.2 方式二:listen 方式
当一个入口只对应一组后端 时,可以直接用 listen 把前端 + 后端写在一起,更简洁:
bash
[root@localhost ~]# vim /etc/haproxy/haproxy.cfg
listen webcluster
bind *:80
mode http
balance roundrobin
server web1 193.168.0.10:80 check
server web2 193.168.0.20:80 check
[root@localhost ~]# systemctl restart haproxy.service

4.3 两种方式如何选
| 对比 | frontend + backend |
listen |
|---|---|---|
| 结构 | 入口与后端分离 | 前端后端写在一起 |
| 适用场景 | 一个入口按规则分发到多个后端组(ACL 分流) | 一个入口对应一组后端 |
| 可读性 | 复杂场景清晰 | 简单场景省事 |
| 灵活性 | 高 | 低 |
4.4 效果验证
客户端(Windows 本机)多次访问 http://192.168.17.100,观察请求是否被轮询分发到两台后端:
frontend + backend 方式的测试结果:

listen 方式的测试结果:

五、远程日志采集
5.1 为什么要做远程日志
HAProxy 的日志默认通过 syslog 协议发送。如果只写在本地,一旦调度器硬盘坏了或系统重装,日志就全没了 。把日志集中采集到一台专门的日志服务器上,既能长期保存,也方便统一分析、安全审计。
架构图:
HAProxy ──UDP 514 端口──▶ 日志采集服务器(rsyslog) (生产日志) (统一存储、归档、审计)
5.2 日志采集服务器配置
在日志服务器上,打开 rsyslog 的 UDP 接收模块:
bash
[root@localhost ~]# vim /etc/rsyslog.conf
module(load="imudp") # needs to be done just once
input(type="imudp" port="514")
[root@localhost ~]# systemctl restart rsyslog.service
| 配置行 | 含义 |
|---|---|
module(load="imudp") |
加载 imudp 模块,让 rsyslog 支持接收 UDP 日志 |
input(type="imudp" port="514") |
在 UDP 514 端口上监听,接收网络传来的日志 |
systemctl restart rsyslog.service |
重启 rsyslog 使配置生效 |

5.3 HAProxy 端配置
在 HAProxy 的 global 段里,把日志发送目标指向日志服务器:
bash
[root@localhost ~]# vim /etc/haproxy/haproxy.cfg
# local2.* /var/log/haproxy.log
#
log 193.168.0.10 local2
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 4000
[root@localhost ~]# systemctl restart haproxy.service
讲解:
log 193.168.0.10 local2------ 把日志通过 UDP 发给 193.168.0.10,设施为local2;- 配置里被
#注释掉的那行local2.* /var/log/haproxy.log是本地写文件的写法,如果要日志同时写本地,取消注释即可; chroot / pidfile / maxconn等是 global 段的其它已有配置,这里一并展示。
5.4 验证
在日志采集服务器上查看收集到的日志:
bash
[root@localhost ~]# cat /var/log/remote/193.168.0.100/root.log
Aug 6 16:38:56 193.168.0.100 root: haproxy udp test
Aug 6 16:44:34 193.168.0.100 root: haproxy udp test 2
[root@localhost ~]#
能看到日志文件里出现了 193.168.0.100(HAProxy) 发来的记录,说明远程日志链路已经打通。
六、socat 热更新:不停机动态调整
6.1 原理
日常运维中,可能要临时调整权重、把某台后端下线维护、再上线。如果每次都改配置重启 HAProxy,会造成短暂的服务中断(连接断开)。
HAProxy 提供了热更新 能力:它通过 global 段里的 stats socket 暴露了一个 Unix 套接字 (管理通道),我们只要用 socat 往这个套接字里发一条文本命令,就能在线 调整运行中的实例,全程无需重启。
socat(命令工具) ──写命令──▶ /var/lib/haproxy/stats(套接字) ──▶ HAProxy 进程(即时生效)
6.2 实战命令
先安装 socat,再开启套接字(mode 600 level admin 表示权限 600、管理员级别,可执行修改类命令):
bash
[root@localhost ~]# dnf install socat
[root@localhost /]# echo "show info" | socat stdio /var/lib/haproxy/stats #查看状态
[root@localhost /]# echo "show servers state" | socat stdio /var/lib/haproxy/stats #查看集群
[root@localhost /]# echo "get weight webcluster/web1" | socat stdio /var/lib/haproxy/stats #查看权重
1 (initial 1) #前面是生效权重,后面是设置权重
# turn on stats unix socket
stats socket /var/lib/haproxy/stats mode 600 level admin #设置套接字
[root@localhost /]# systemctl restart haproxy.service
[root@localhost /]# echo "set weight webcluster/web1 2" | socat stdio /var/lib/haproxy/stats #修改权重
[root@localhost /]# echo "disable server webcluster/web1" | socat stdio /var/lib/haproxy/stats #指定服务器下线
[root@localhost /]# echo "enable server webcluster/web1" | socat stdio /var/lib/haproxy/stats #指定服务器上线
命令语法拆解:
bash
echo "命令文本" | socat stdio /var/lib/haproxy/stats
# ↑ 要执行的HAProxy命令 ↑ ↑ 连到管理套接字
常用命令:
| socat 命令 | 作用 |
|---|---|
show info |
查看 HAProxy 运行状态、连接数、版本等信息 |
show servers state |
查看集群中所有后端服务器的状态 |
get weight <组>/<服务器> |
查看某台服务器的权重 |
set weight <组>/<服务器> <值> |
在线修改权重(无需重启) |
disable server <组>/<服务器> |
在线摘除服务器(下线维护) |
enable server <组>/<服务器> |
在线恢复服务器(重新上线) |
⚠️ 注意:
get weight输出1 (initial 1),前面是当前生效权重,括号里是配置文件中设置的初始权重。
stats socket ... level admin里的level admin决定了权限级别,只有 admin 级别才能执行 set / disable / enable 等写操作。
七、状态页(Web 监控面板)
7.1 配置状态页
HAProxy 自带一个 Web 状态页,能实时看到每台后端的健康状态、请求数、连接数。配置一个专门用于监控的 listen 实例:
bash
[root@localhost /]# vim /etc/haproxy/haproxy.cfg
listen stats_page
bind *:4444
mode http
stats enable #开启状态页
log global #全局配置日志
stats refresh 1s #自动刷新,1秒一次
stats uri /stats #自定义uri
stats auth li:li #认证,支持多行
[root@localhost /]# systemctl restart haproxy.service
| 配置行 | 含义 |
|---|---|
bind *:4444 |
状态页监听在 4444 端口(独立端口,不与业务混用) |
stats enable |
开启状态页功能 |
stats refresh 1s |
页面每秒自动刷新一次 |
stats uri /stats |
状态页的访问路径,访问 http://IP:4444/stats |
stats auth li:li |
访问认证(用户名:密码),可写多行加多组账号 |
7.2 状态页怎么看
浏览器访问 http://192.168.17.100:4444/stats,输入账号密码进入:

| 字段 | 含义 | 关注点 |
|---|---|---|
每台 web1 / web2 行 |
各后端服务器的实时状态 | 看 Status 列 |
| Status = UP | 服务器健康,正在服务 | ✅ 正常 |
| Status = DOWN | 健康检查失败,已被摘除 | 🔴 排查后端问题 |
| Status = MAINT | 维护状态(被 disable) | 🟠 主动下线中 |
| Sessions / Bytes 等 | 会话数、流量统计 | 观察负载分布是否均匀 |
八、客户端真实 IP 透传
8.1 为什么要透传真实 IP
默认情况下,后端服务器看到的所有请求来源都是 HAProxy 的 IP(193.168.0.100),而不是真实客户端 IP。这会带来两个问题:
- 日志失真 ------ 后端 access.log 里全是代理 IP,没法做访问分析、地域统计;
- 安全失控 ------ 风控、防 CC 攻击、IP 黑名单全都失效,因为根本不知道谁在访问。
所以"让后端能看到真实客户端 IP "是生产环境的硬需求。而具体怎么做,取决于 HAProxy 工作在七层还是四层:
| 工作模式 | 透传手段 | 原理 |
|---|---|---|
| 七层(HTTP) | X-Forwarded-For 头 | 在 HTTP 请求头里附加客户真实 IP |
| 四层(TCP) | PROXY Protocol | 在 TCP 连接建立后,先发一行带客户端 IP 的文本头 |
8.2 七层透传:X-Forwarded-For
原理
客户端(192.168.17.1) → HAProxy ──在请求头追加 X-Forwarded-For: 192.168.17.1──▶ 后端
只需要在 HAProxy 的 defaults 里开启 option forwardfor,后端再配合读取该头即可。
8.2.1 后端是 Nginx
① 透传前:后端 Nginx 的 access.log 里全是代理 IP:
bash
[root@localhost ~]# cat /var/log/nginx/access.log
193.168.0.100 - - [06/Aug/2026:17:07:03 +0800] "GET / HTTP/1.1" 200 18 "-" "curl/8.4.0" "-"
193.168.0.100 - - [06/Aug/2026:17:07:03 +0800] "GET / HTTP/1.1" 200 18 "-" "curl/8.4.0" "-"
193.168.0.100 - - [06/Aug/2026:17:07:10 +0800] "GET / HTTP/1.1" 200 18 "-" "curl/8.4.0" "-"
① 的截图验证:

② 在 HAProxy 开启 forwardfor:
bash
[root@localhost ~]# vim /etc/haproxy/haproxy.cfg
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8 #配置这条命令
option redispatch
retries 3
[root@localhost ~]# systemctl restart haproxy.service
#rs服务器上
[root@localhost ~]# cat /var/log/nginx/access.log
193.168.0.100 - - [06/Aug/2026:17:09:07 +0800] "GET / HTTP/1.1" 200 18 "-" "curl/8.4.0" "192.168.17.1"
193.168.0.100 - - [06/Aug/2026:17:09:07 +0800] "GET / HTTP/1.1" 200 18 "-" "curl/8.4.0" "192.168.17.1"
193.168.0.100 - - [06/Aug/2026:17:09:14 +0800] "GET / HTTP/1.1" 200 18 "-" "curl/8.4.0" "192.168.17.1"
② 的截图验证:

③ 结果对比 :日志末尾 多出来的 "192.168.17.1" 就是真实客户端 IP!
原理:Nginx 默认的
combined日志格式里有$http_x_forwarded_for字段,HAProxy 追加了X-Forwarded-For头,Nginx 就把它打印到了日志末尾。HAProxy 开启 → 后端自动就能看到,不需要改 Nginx 配置。
8.2.2 后端是 Apache
Apache 需要手动改日志格式 ,把 X-Forwarded-For 加进 combined 格式里:
bash
[root@localhost ~]# vim /etc/haproxy/haproxy.cfg
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8 #配置这条命令
option redispatch
retries 3
[root@localhost ~]# systemctl restart haproxy.service
[root@localhost ~]# vim /etc/httpd/conf/httpd.conf
<IfModule log_config_module>
#
# The following directives define some format nicknames for use with
# a CustomLog directive (see below).
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" \"%{X-Forwarded-For}i\" " combined #添加X-Forwarded-For
LogFormat "%h %l %u %t \"%r\" %>s %b" common
[root@localhost ~]# systemctl restart httpd
[root@localhost ~]# cat /etc/httpd/logs/default.log
193.168.0.100 - - [09/Aug/2026:11:35:21 +0800] "GET / HTTP/1.1" 200 18 "-" "curl/8.4.0"
193.168.0.100 - - [09/Aug/2026:11:35:22 +0800] "GET / HTTP/1.1" 200 18 "-" "curl/8.4.0"
193.168.0.100 - - [09/Aug/2026:11:35:23 +0800] "GET / HTTP/1.1" 200 18 "-" "curl/8.4.0"
193.168.0.100 - - [09/Aug/2026:11:35:24 +0800] "GET / HTTP/1.1" 200 18 "-" "curl/8.4.0"
193.168.0.100 - - [09/Aug/2026:11:41:25 +0800] "GET / HTTP/1.1" 200 18 "-" "curl/8.4.0" "192.168.17.1"
193.168.0.100 - - [09/Aug/2026:11:41:26 +0800] "GET / HTTP/1.1" 200 18 "-" "curl/8.4.0" "192.168.17.1"
193.168.0.100 - - [09/Aug/2026:11:41:27 +0800] "GET / HTTP/1.1" 200 18 "-" "curl/8.4.0" "192.168.17.1"
修改后的截图验证:

讲解:
- 关键改动是 combined 格式末尾加的
"%{X-Forwarded-For}i",i表示读请求头(incoming header); - 对比上下的日志:修改后,日志末尾出现了
"192.168.17.1"真实客户端 IP。
8.3 四层透传:PROXY Protocol
原理
四层模式下 HAProxy 不解析 HTTP,没法用 X-Forwarded-For 头。解决办法是 PROXY Protocol:HAProxy 在和后端建立 TCP 连接后、发送业务数据前,先给后端发一行特殊文本,格式类似:
PROXY TCP4 192.168.17.1 193.168.0.100 51234 80\r\n
↑ 协议版本 ↑真实客户端IP ↑后端IP ↑源端口 ↑目的端口
后端开启 proxy_protocol 接收能力后,就能从这行文本里取出真实 IP。适用于数据库、Redis、裸 TCP 等七层无法处理的场景。
客户端(192.168.17.1) ──TCP──▶ HAProxy ──先发一行PROXY头──▶ 后端(解析出真实IP)
8.3.1 HAProxy 端(核心)
在 server 行加 send-proxy 参数,告诉 HAProxy 往后端发 PROXY 头;同时模式改为 mode tcp:
bash
[root@localhost ~]# vim /etc/haproxy/haproxy.cfg
listen webcluster
bind *:80
mode tcp
balance roundrobin
server web1 193.168.0.10:80 send-proxy check
server web2 193.168.0.20:80 send-proxy check
[root@localhost ~]# systemctl restart httpd
8.3.2 后端 Nginx
在 Nginx 的 server 块里加 proxy_protocol,表示"我接收 PROXY 头":
bash
[root@localhost ~]# vim /etc/nginx/nginx.conf
# for more information.
include /etc/nginx/conf.d/*.conf;
server {
listen 80 proxy_protocol; #只能通过四层访问
listen [::]:80;
server_name _;
root /usr/share/nginx/html;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
[root@localhost ~]# systemctl restart nginx
加了
listen 80 proxy_protocol后,Nginx 只接受带 PROXY 头的连接,直接访问后端 IP:80 反而会报错
8.3.3 后端 Apache
Apache 需要加载 mod_remoteip 模块并开启 PROXY 解析:
bash
[root@localhost ~]# vim /etc/httpd/conf.modules.d/10-proxy_h2.conf
LoadModule proxy_http2_module modules/mod_proxy_http2.so
LoadModule remoteip_module modules/mod_remoteip.so
[root@localhost ~]# vim /etc/httpd/conf/httpd.conf
RemoteIPProxyProtocol on
RemoteIPTrustedProxy 192.168.17.0/24 #信任谁来访问
[root@localhost ~]# systemctl restart httpd
| 配置行 | 含义 |
|---|---|
LoadModule remoteip_module ... |
加载 remoteip 模块,Apache 才有能力解析 PROXY 头 |
RemoteIPProxyProtocol on |
开启 PROXY Protocol 解析 |
RemoteIPTrustedProxy 192.168.17.0/24 |
只信任来自这个网段的 PROXY 头------安全关键配置!防止外部伪造 PROXY 头冒充任意 IP |
RemoteIPTrustedProxy必须配置且写准确,否则任何人都能伪造PROXY头,把自己"伪装"成任意客户端 IP,绕过 IP 风控。信任来源永远要最小化。
8.4 效果验证
Apache 后端日志中,来源 IP 已变成真实客户端 192.168.17.1:
bash
[root@localhost ~]# cat /etc/httpd/logs/default.log
192.168.17.1 - - [09/Aug/2026:12:07:25 +0800] "GET / HTTP/1.1" 200 18 "-" "curl/8.4.0" "-"
192.168.17.1 - - [09/Aug/2026:12:10:46 +0800] "GET / HTTP/1.1" 200 18 "-" "curl/8.4.0" "-"
192.168.17.1 - - [09/Aug/2026:12:10:46 +0800] "GET / HTTP/1.1" 200 18 "-" "curl/8.4.0" "-"
Nginx 后端 需要把 $proxy_protocol_addr 写进日志格式才能取到 PROXY 头里的 IP:
bash
[root@localhost ~]# vim /etc/nginx/nginx.conf
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'"$proxy_protocol_addr"'
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
[root@localhost ~]# systemctl restart nginx.service
[root@localhost ~]# cat /var/log/nginx/access.log
193.168.0.100 - - [09/Aug/2026:12:21:37 +0800] "GET / HTTP/1.1" "192.168.17.1"200 18 "-" "curl/8.4.0" "-"
193.168.0.100 - - [09/Aug/2026:12:21:37 +0800] "GET / HTTP/1.1" "192.168.17.1"200 18 "-" "curl/8.4.0" "-"
193.168.0.100 - - [09/Aug/2026:12:21:38 +0800] "GET / HTTP/1.1" "192.168.17.1"200 18 "-" "curl/8.4.0" "-"
193.168.0.100 - - [09/Aug/2026:12:21:39 +0800] "GET / HTTP/1.1" "192.168.17.1"200 18 "-" "curl/8.4.0" "-"
日志里 "192.168.17.1" 就是 PROXY 头解析出的真实客户端 IP($proxy_protocol_addr)。
8.5 七层 vs 四层透传对比小结
| 维度 | 七层 X-Forwarded-For | 四层 PROXY Protocol |
|---|---|---|
| 适用模式 | mode http |
mode tcp |
| 实现机制 | 在 HTTP 头追加字段 | 在 TCP 流开头发文本头 |
| HAProxy 配置 | option forwardfor |
server ... send-proxy |
| 后端配置 | Nginx 默认支持 / Apache 改 LogFormat | Nginx proxy_protocol / Apache mod_remoteip |
| 风险点 | 头可被伪造,需信任链 | 头可被伪造,需 RemoteIPTrustedProxy 限制 |
| 典型场景 | Web 网站访问分析 | 数据库、Redis、裸 TCP |
九、负载均衡算法详解
HAProxy 通过 balance 参数指定后端调度算法,balance 可以写在 listen 或 backend 段里。
算法分成三大类:静态算法、动态算法、哈希类(混合)算法。
┌─ 静态算法:static-rr、first(运行时不改权重)
HAProxy 调度算法 ─┼─ 动态算法:roundrobin、leastconn(支持运行时调整)
└─ 哈希算法:source、uri、url_param、hdr(按内容做hash)
9.1 静态算法
static-rr:基于权重的轮询调度
按权重依次轮流分发,规则固定、从头到尾一个节奏。
- 不支持运行时用 socat 动态调整权重(只支持 0 和 1);
- 不支持后端服务器"慢启动";
- 后端主机数量没有限制;
- 相当于 LVS 中的 wrr(加权轮询)。
记忆:
static-rr名字里的static(静态)就是它的宿命------配置时定死,运行时改不动。
first:优先填满第一台
- 根据服务器在列表中的位置,自上而下调度;
- 只有当前服务器的连接数达到上限,新请求才分给下一台;
- 会忽略服务器的权重设置;
- 不支持用 socat 动态修改权重(可设置 0、1,其它值无效)。
记忆:
first就像"第一台没满就永远先发给第一台",适合做热备叠加(后加的机器吃剩余流量)。
9.2 动态算法
roundrobin:加权轮询(默认算法)
最常用的动态轮询,支持运行时改权重。
- 基于权重的轮询动态调度算法;
- 支持权重的运行时调整(区别于 LVS 的 rr 轮询);
- 支持慢启动(新上线的服务器权重逐渐增加,避免刚上线就被打爆);
- 每个 backend 中最多支持 4095 个 real server;
- 支持对 real server 权重动态调整;
- roundrobin 是默认调度算法,使用最广泛。
记忆:动态轮询 = 能"边跑边改"的轮询。
leastconn:最少连接
优先把新连接分给当前活跃连接数最少的后端。
- 加权的最少连接动态调度算法;
- 支持权重的运行时调整和慢启动;
- 根据当前连接最少的后端服务器而非权重进行优先调度(对新客户端连接而言);
- 比较适合长连接场景,如:MySQL、Redis。
记忆:轮询是"按顺序",leastconn 是"谁最闲给谁"。短请求轮询更公平,长连接一定要用 leastconn,否则所有连接都堆在第一台。
9.3 哈希类算法(混合)
source:基于源地址哈希
对客户端源 IP 做 hash,再把结果映射到后端。
- 基于源地址 hash,将请求转发到后端服务器,后续同一个源地址的请求会被转发到同一台后端服务器;
- 弊端:后端服务器上线或下线会导致 hash 结果偏移,大量请求可能被重新分配(对应"雪崩式迁移")。
记忆:source 的天然副作用是"同一用户永远粘在同一台机器",所以它也能当会话保持用,但后端增减会导致映射大乱。
一致性哈希(解决 source 的弊端)
当服务器总权重发生变化时,使用一致性哈希后,对调度结果的影响是局部的,不会引起大的变动------只有被增删节点"命中的那一小段"请求才需要重新分配。
普通取模hash: 1 2 3 4 5 6 7 8 → 去掉一台 → 全部重排,所有请求大搬家
一致性hash: (哈希环上按段分配)→ 去掉一台 → 只有该节点附近的一小段受影响
配置方法,在 backend 里加上:
ini
hash-type consistent
- 该 hash 算法是动态的,支持 socat 等工具在线调整;
- 支持慢启动。
uri:基于 URI 哈希
对用户请求的 URI(左半部分或整个 URI)做 hash,再对总权重取模后分发。
- 适用于后端是缓存服务器的场景(同一 URL 永远到同一台缓存,命中率高);
- 默认是静态算法,也可以通过
hash-type map-based和hash-type consistent,选择使用取模法 还是一致性哈希。
url_param:基于 URL 参数哈希
对用户请求 URL 的 params 部分中某个参数 key 对应的 value 做 hash 计算,再除以总权重派发。
- 后端处理同一数据会被调度到同一个服务器;
- 多用于电商 ------比如按
user_id参数把同一用户的请求固定到同一台机器。
hdr:基于 HTTP 头部哈希
针对用户每个 HTTP 头部(Header)请求中指定信息做 hash,再由服务器总权重取模后派发。
- 如果请求头里没有有效值,则使用默认的轮询调度。
记忆:hdr = "看头不看身",可以按
Cookie、User-Agent等任意请求头做粘滞。
9.4 算法选型总结
| 算法 | 类型 | 适用场景 | 是否支持运行时改权重 |
|---|---|---|---|
static-rr |
静态 | 简单轮询、后端固定不变 | ❌ |
first |
静态 | 第一台打满再用第二台 | ❌ |
roundrobin |
动态 | 通用 Web 默认首选 | ✅ |
leastconn |
动态 | 长连接:MySQL / Redis | ✅ |
source |
哈希 | 会话粘滞、同一来源固定后端 | ✅ |
uri |
哈希 | 缓存服务、CDN 命中 | ✅ |
url_param |
哈希 | 电商按用户参数分流 | ✅ |
hdr |
哈希 | 按请求头分流 | ✅ |
十、基于 Cookie 的会话保持
10.1 为什么需要会话保持
很多网站有"登录状态":用户在 A 机器上登录了,下次请求如果被轮询分到 B 机器,B 上没有他的登录信息,就会掉线。
解决办法就是会话保持(Session Sticky):让同一个用户的所有请求都发给同一台后端。
Cookie 会话保持的原理:
第1次请求:客户端 ──▶ HAProxy(后端任选一台 web1,并在响应里种一个 Cookie)
第2次请求:客户端带着Cookie ──▶ HAProxy(一看Cookie,直接转发给 web1)
cookie value 指令就是为当前 server 指定 Cookie 值,实现基于 Cookie 的会话粘滞。它与 source 算法比,对客户端的区分粒度更精准(source 按 IP,NAT 下多台用户共享一个 IP 就分不开了),但同时也加大了 HAProxy 的负担。目前此模式使用较少,很多场景已被 session 共享服务器(如 Redis)替代。
10.2 cookie 指令详解
ini
cookie name [ rewrite | insert | prefix ] [ indirect ] [ nocache ] [ postonly ] [ preserve ] [ httponly ] [ secure ] [ domain ]* [maxidle <idle> ] [ maxlif ]
name #cookie的key名称,用于实现持久连接
insert #插入新的cookie,默认不插入cookie
indirect #如果客户端已经有cookie,则不会再发送cookie信息
nocache #当client和paharxy之间有缓存服务器时,不允许中间缓存器缓存cookie,因为这会导致很多经过同一个CDN的请求都发送到同一台后端服务器
| 选项 | 含义 |
|---|---|
name |
Cookie 的 key 名称,用于实现持久连接 |
insert |
由 HAProxy 插入一个新的 Cookie(默认不插入) |
indirect |
如果客户端已经有该 Cookie,就不再下发 Cookie 信息 |
nocache |
客户端与 HAProxy 之间有缓存服务器(CDN)时,禁止中间缓存器缓存 Cookie------否则很多经过同一个 CDN 的请求会被固定到同一台后端,破坏负载均衡 |
10.3 配置实战
bash
[root@localhost ~]# vim /etc/haproxy/haproxy.cfg
listen webcluster
bind *:80
mode http
balance roundrobin
cookie WEBCOOKIE insert nocache indirect
server web1 193.168.0.10:80 cookie server1 weight 1 check inter 2s fall 3 rise 3
server web2 193.168.0.20:80 cookie server2 weight 1 check inter 2s fall 3 rise 3
[root@localhost ~]# systemctl restart haproxy.service
关键行解读:
cookie WEBCOOKIE insert nocache indirect------ 设置名为WEBCOOKIE的会话保持 Cookie;server web1 ... cookie server1------ 给 web1 一个 Cookie 值server1;server web2 ... cookie server2------ 给 web2 一个 Cookie 值server2。
测试效果:第一次访问后,响应里出现 Set-Cookie: WEBCOOKIE=server1;刷新后再访问,请求头里带着该 Cookie,就会被固定转发到 web1:

带 Cookie 后无论刷新多少次,都稳定落在同一台后端上,说明会话保持生效了。
十一、自定义错误页面
11.1 默认错误页
HAProxy 自带一套默认错误页面,放在 /usr/share/haproxy/ 目录下:
bash
[root@localhost ~]# cd /usr/share/haproxy/ #默认错误页面位置
[root@localhost haproxy]# ls
400.http 403.http 408.http 500.http 502.http 503.http 504.http README
[root@localhost haproxy]#
文件名就是状态码:400 / 403 / 408 / 500 / 502 / 503 / 504。当后端全部宕机时,HAProxy 返回 503(Service Unavailable)。
11.2 自定义错误页
步骤分两步:
第 1 步:写一个带完整 HTTP 响应头的错误页文件
bash
[root@localhost ~]# mkdir /htmlerr/
[root@localhost ~]# vim /htmlerr/503.errpage
HTTP/1.0 503 Service Unavailable
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<html><body><h1>页面错误,请耐心等待</h1>
No server is available to handle this request.
</body></html>
⚠️ 注意:文件必须手动写全 HTTP 响应头(状态行、Cache-Control、Content-Type 等),因为 HAProxy 是原样把整个文件发给客户端的。
第 2 步:用 errorfile 指令把状态码指向自定义文件
bash
errorfile <code> <file>
<code> #HTTP status code,支持200,400,403,405,408,425,429,500,502,503,504等
<file> #包含完整HTTP响应头的错误页文件的绝对路径
[root@localhost ~]# vim /etc/haproxy/haproxy.cfg
defaults
errorfile 503 /htmlerr/503.errpage
[root@localhost ~]# systemctl restart haproxy.service
语法讲解:
| 参数 | 含义 |
|---|---|
errorfile |
指定某状态码使用自定义错误页 |
503 |
要替换的 HTTP 状态码(支持 200/400/403/405/408/425/429/500/502/503/504) |
/htmlerr/503.errpage |
自定义错误页文件的绝对路径 |
测试效果:停掉两台后端,访问网站看到的就是你自定义的 503 页面:

十二、ACL 访问控制
12.1 ACL 是什么
ACL(Access Control List,访问控制列表) 是一种基于包过滤的访问控制技术。
它可以根据设定的条件对经过服务器的数据包进行过滤(条件匹配),即对接收到的报文进行匹配和过滤,基于请求报文头部中的源地址、源端口、目标地址、目标端口、请求方法、URL、文件后缀等信息内容进行匹配,并执行进一步操作,比如允许其通过或丢弃。
通俗讲:ACL = 给请求"体检",体检合格(匹配)就发到 A 组后端,不合格就发到 B 组或直接拒绝。它是 HAProxy 实现"按规则分流"的核心武器。
12.2 ACL 语法
bash
#用acl来定义或声明一个acl
acl <aclname> <criterion> [flags] [operator] [<value>]
acl 名称 匹配规范 匹配模式 具体操作符 操作对象类型
语法结构图:
acl test path_end -m sub /a
│ │ │ │ │ │
名称 匹配规范 匹配模式 操作符 匹配值
| 部分 | 含义 |
|---|---|
aclname |
ACL 名称,自定义,后面要用它引用 |
criterion |
匹配规范,按什么字段匹配(如 hdr_end 匹配请求头结尾、path_end 匹配路径结尾) |
flags |
匹配模式(如 -i 忽略大小写、-m 指定匹配方法) |
operator |
操作符(如 sub 子串匹配) |
value |
匹配的对象值 |
ACL 名称命名规则:
bash
acl test path_end -m sub /a
#ACL名称可以使用大写A-Z、小写a-z、数字0-9、冒号:、点.、中横线和下划线,并严格区分大小写
即:名称可用
A-Z a-z 0-9 : . - _,且严格区分大小写。
12.3 实战:按域名分发到不同后端
需求:客户端访问的 Host 以 .com 结尾 → 分发到 webserver1;否则 → 分发到默认后端。
bash
[root@localhost ~]# vim /etc/haproxy/haproxy.cfg
frontend webcluster
bind *:80
mode http
acl test hdr_end(host) -i .com #Host 以 .com 结尾(不分大小写)
use_backend webserver1 if test #匹配
default_backend webserverdefault #不匹配
backend webserver1
server web1 193.168.0.10:80 check inter 2s fall 2 rise 3
backend webserverdefault
server web2 193.168.0.20:80 check inter 2s fall 2 rise 3
[root@localhost ~]# systemctl restart haproxy.service
逐行解读:
| 行 | 含义 |
|---|---|
acl test hdr_end(host) -i .com |
定义名为 test 的 ACL:取请求的 Host 头,看它是否以 .com 结尾 ,-i 表示忽略大小写 |
use_backend webserver1 if test |
如果匹配 test 这个 ACL → 转发到 webserver1 |
default_backend webserverdefault |
否则(默认) → 转发到 webserverdefault |
check inter 2s fall 2 rise 3 |
健康检查:每 2 秒一次,连续失败 2 次下线,连续成功 3 次上线 |
测试效果:
用不同的域名(Host 头)访问同一 IP,看请求被分流到不同后端:
以 .com 结尾的域名(应命中 webserver1):

以 .org 结尾的域名(不命中 test,走默认 webserverdefault):

十三、四层负载实战:MariaDB 数据库集群
四层(TCP)负载------HAProxy 不关心数据库协议内容,只负责把 TCP 连接转发给数据库后端。
13.1 架构
客户端 ──▶ HAProxy :3306(mode tcp)──▶ db1(193.168.0.10:3306, server-id=10)
└──▶ db2(193.168.0.20:3306, server-id=20)
13.2 RS1、RS2:安装并初始化 MariaDB
在两台后端上安装 MariaDB,并设置不同的 server-id(主从复制识别用,这里用来区分验证):
bash
[root@localhost ~]# dnf install mariadb-server
[root@localhost ~]# vim /etc/my.cnf.d/mariadb-server.cnf
[mysqld]
server-id=10
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
log-error=/var/log/mariadb/mariadb.log
pid-file=/run/mariadb/mariadb.pid
[root@localhost ~]# systemctl start mariadb.service
[root@localhost ~]# mysql
MariaDB [(none)]> create user root@'%' identified by '123'
说明:db1 配
server-id=10,db2 把server-id改成20,这样查询@@server_id时能区分到底连到了哪一台。同时创建一个可从任意主机登录的 root 账号(root@'%'),方便从客户端连接。
13.3 HAProxy:四层数据库负载
在 HAProxy 配置里新增一个 listen,mode tcp 是关键------对数据库这种非 HTTP 协议,必须用四层模式:
bash
[root@localhost system-connections]# vim /etc/haproxy/haproxy.cfg
listen dbcluster
bind *:3306
mode tcp
balance roundrobin
server db1 193.168.0.10:3306 check inter 2s fall 3 rise 3
server db2 193.168.0.20:3306 check inter 2s fall 3 rise 3
[root@localhost system-connections]# systemctl restart haproxy.service
讲解:
| 行 | 含义 |
|---|---|
bind *:3306 |
监听 3306 端口(MySQL 默认端口) |
mode tcp |
四层 TCP 模式,不解析数据库协议,只转发流量 |
server db1 / db2 ... check inter 2s fall 3 rise 3 |
两台数据库后端,健康检查 2 秒一次,失败 3 次下线、成功 3 次上线 |
13.4 验证
客户端通过 HAProxy 地址连数据库,查询 server_id 确认请求被轮询分发到了两台不同的数据库:
bash
[root@localhost ~]# mysql -uroot -p123 -h192.168.17.100
MariaDB [(none)]> select @@server_id;
+-------------+
| @@server_id |
+-------------+
| 10 |
+-------------+
[root@localhost ~]# mysql -uroot -p123 -h192.168.17.100
MariaDB [(none)]> select @@server_id;
+-------------+
| @@server_id |
+-------------+
| 20 |
+-------------+
第一次返回 10、第二次返回 20,说明 HAProxy 在 db1 和 db2 之间做了轮询------四层数据库负载均衡成功!

十四、全站 HTTPS 加密
14.1 HTTPS 终止原理
SSL 卸载(SSL Termination) :客户端和 HAProxy 之间跑 HTTPS(443) ,HAProxy 负责解密;HAProxy 和后端之间跑普通 HTTP(80),后端不需要处理加解密。
客户端 ──HTTPS(443,加密)──▶ HAProxy(解密) ──HTTP(80,明文)──▶ 后端
这样做的好处:
- 证书只需维护一份(放在 HAProxy 上),后端不用各自配证书;
- 减轻后端 CPU 负担(加解密最耗 CPU,集中到 HAProxy 处理);
- 全站加密,传输过程不被窃听。
14.2 生成自签名证书
bash
mkdir /etc/haproxy/certs/
openssl req -newkey rsa:2048 -nodes -sha256 -keyout /etc/haproxy/certs/qw.key -x509 -days 365 -out /etc/haproxy/certs/qw.crt
[root@localhost ~]# cat /etc/haproxy/certs/qw.key /etc/haproxy/certs/qw.crt > /etc/haproxy/qw.pem
命令讲解:
| 命令 | 作用 |
|---|---|
mkdir /etc/haproxy/certs/ |
建证书存放目录 |
openssl req ... -keyout qw.key ... -out qw.crt |
生成 RSA 2048 位 自签名证书:-newkey rsa:2048 生成新密钥,-x509 生成自签名证书,-days 365 有效期一年 |
cat qw.key qw.crt > qw.pem |
把私钥和证书合并成一个 PEM 文件------HAProxy 要求证书和私钥在同一个文件里 |
14.3 配置 HAProxy
bash
[root@localhost ~]# vim /etc/haproxy/haproxy.cfg
listen webcluster-80
bind *:80
mode http
redirect scheme https if !{ ssl_fc }
frontend webcluster
bind *:443 ssl crt /etc/haproxy/qw.pem
mode http
use_backend webserver
backend webserver
server web1 193.168.0.10:80 check inter 2s fall 2 rise 3
server web2 193.168.0.20:80 check inter 2s fall 2 rise 3
[root@localhost ~]# systemctl restart haproxy.service
逐行解读:
| 配置行 | 含义 |
|---|---|
listen webcluster-80 |
专门处理 80 端口(HTTP)的实例 |
redirect scheme https if !{ ssl_fc } |
HTTP 强制跳转 HTTPS :ssl_fc 是"当前连接已启用 SSL"的标记,!{ ssl_fc } 表示"不是 SSL 连接",此时强制跳转到 https |
bind *:443 ssl crt /etc/haproxy/qw.pem |
在 443 端口启用 SSL,使用之前生成的 qw.pem 证书 |
use_backend webserver |
解密后的请求转给 webserver 后端(后端仍是普通 HTTP) |
14.4 验证
