为什么是 HAProxy + Keepalived
Nginx 擅长七层反向代理,但在纯 TCP 负载均衡、连接级健康检查、会话保持等场景下,HAProxy 才是专业选择。Keepalived 提供的 VRRP 虚拟 IP 漂移则解决了单点故障问题。两者组合是 Linux 下最经典的四层/七层负载均衡 + 高可用方案。
| 维度 | Nginx | HAProxy | LVS |
|---|---|---|---|
| 工作层 | 七层为主 | 四层+七层 | 四层(内核态) |
| 健康检查 | 被动 | 主动+被动 | 有限 |
| 会话保持 | cookie/ip_hash | cookie/source/conn | source |
| 动态后端 | 需 reload | 运行时 socket | ipvsadm |
| 性能 | 极高 | 极高 | 最高(内核) |
| 高可用配对 | upstream + keepalived | keepalived | keepalived/heartbeat |
选型建议:Web/API 七层用 Nginx,数据库/Redis/MQ 等 TCP 负载用 HAProxy,超大规模四层用 LVS。
架构总览
┌──────────────────────────────────────────┐
│ Virtual IP (VIP) │
│ 192.168.1.100 │
│ Keepalived VRRP 主备漂移 │
└─────────┬──────────────┬─────────────────┘
│ │
┌─────────▼────┐ ┌──────▼───────┐
│ HAProxy-Master │ │ HAProxy-Backup │
│ 192.168.1.11 │ │ 192.168.1.12 │
│ Active (MASTER)│ │ Standby(BACKUP)│
└────────┬────────┘ └───────┬───────┘
│ │
┌──────────────┼───────────────────┼──────────────┐
│ │ │ │
┌─────▼────┐ ┌────▼────┐ ┌─────▼────┐ ┌────▼────┐
│ Backend1 │ │ Backend2│ │ Backend3 │ │ Backend4│
│ .21:80 │ │ .22:80 │ │ .23:80 │ │ .24:80 │
└──────────┘ └─────────┘ └──────────┘ └─────────┘
安装部署
环境准备
# 两台负载均衡器均执行
# CentOS/RHEL
yum install -y haproxy keepalived
# Ubuntu/Debian
apt install -y haproxy keepalived
# 开启 IP 转发(四层模式需要)
echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf
sysctl -p
# 关闭 SELinux(或配置策略)
setenforce 0
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
HAProxy 配置
# /etc/haproxy/haproxy.cfg
global
log 127.0.0.1 local2
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 20000
user haproxy
group haproxy
daemon
stats socket /var/lib/haproxy/stats mode 660 level admin
tune.ssl.default-dh-param 2048
defaults
mode http
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 5s
timeout client 30s
timeout server 30s
timeout http-keep-alive 10s
timeout check 5s
maxconn 10000
# ─── 统计页面 ───
listen stats
bind *:8404
mode http
stats enable
stats uri /haproxy-stats
stats realm HAProxy\ Statistics
stats auth admin:StrongPass!2026
stats refresh 10s
# ─── 七层 HTTP 负载均衡 ───
frontend web-frontend
bind *:80
bind *:443 ssl crt /etc/haproxy/certs/scrcx.cn.pem alpn h2,http/1.1
http-request redirect scheme https unless { ssl_fc }
# ACL 规则:按路径分流
acl is_api path_beg /api/
acl is_static path_beg /static/ /images/ /css/ /js/
acl is_blog path_beg /posts/ /tags/ /about
use_backend api_servers if is_api
use_backend static_servers if is_static
default_backend web_servers
# ─── 后端服务器池 ───
backend web_servers
mode http
balance roundrobin
# 主动健康检查
option httpchk GET /healthz
http-check expect status 200
# 会话保持:cookie 注入
cookie SERVERID insert indirect nocache httponly samesite Strict
# 慢启动(新服务器逐步加流量)
server web1 192.168.1.21:80 check cookie s1 weight 10 slowstart 30s
server web2 192.168.1.22:80 check cookie s2 weight 10 slowstart 30s
server web3 192.168.1.23:80 check cookie s3 weight 10 backup
backend api_servers
mode http
balance leastconn
option httpchk GET /api/health
http-check expect status 200
# 连接复用
option http-keep-alive
server api1 192.168.1.31:8080 check maxconn 500
server api2 192.168.1.32:8080 check maxconn 500
backend static_servers
mode http
balance roundrobin
option httpchk GET /static/health.txt
# 缓存友好:尽量复用连接
http-reuse safe
server st1 192.168.1.41:80 check
server st2 192.168.1.42:80 check
# ─── 四层 TCP 负载均衡(数据库示例) ───
listen mysql-cluster
bind *:3306
mode tcp
balance source
option tcp-check
tcp-check connect
tcp-check send PING\r\n
tcp-check expect string *pong*
# 最大连接限制
server mysql-master 192.168.1.51:3306 check inter 2000 rise 2 fall 3 maxconn 200
server mysql-slave 192.168.1.52:3306 check inter 2000 rise 2 fall 3 maxconn 200 backup
listen redis-cluster
bind *:6379
mode tcp
balance first
option tcp-check
tcp-check send PING\r\n
tcp-check expect string +PONG
server redis1 192.168.1.61:6379 check inter 1000 fall 2
server redis2 192.168.1.62:6379 check inter 1000 fall 2
Keepalived 配置
主节点(Master)配置:
# /etc/keepalived/keepalived.conf — 主节点
global_defs {
router_id LB_MASTER
# 启用脚本安全权限
enable_script_security
}
# ─── HAProxy 健康检查脚本 ───
vrrp_script check_haproxy {
script "/etc/keepalived/check_haproxy.sh"
interval 2
weight -30 # 脚本失败则优先级减 30
fall 2
rise 2
}
# ─── VRRP 实例 ───
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
advert_int 1
# 抢占模式:Master 恢复后自动夺回 VIP
preempt
preempt_delay 5 # 恢复后等 5 秒再抢占
authentication {
auth_type PASS
auth_pass MyStr0ngVRRP
}
virtual_ipaddress {
192.168.1.100/24 dev eth0 label eth0:vip
}
# 跟踪 HAProxy 进程状态
track_script {
check_haproxy
}
# 状态变更通知脚本
notify_master "/etc/keepalived/notify.sh master"
notify_backup "/etc/keepalived/notify.sh backup"
notify_fault "/etc/keepalived/notify.sh fault"
}
备节点(Backup)配置:
# /etc/keepalived/keepalived.conf — 备节点
global_defs {
router_id LB_BACKUP
enable_script_security
}
vrrp_script check_haproxy {
script "/etc/keepalived/check_haproxy.sh"
interval 2
weight -30
fall 2
rise 2
}
vrrp_instance VI_1 {
state BACKUP # 关键差异
interface eth0
virtual_router_id 51 # 必须与 Master 一致
priority 90 # 低于 Master
advert_int 1
preempt
preempt_delay 5
authentication {
auth_type PASS
auth_pass MyStr0ngVRRP
}
virtual_ipaddress {
192.168.1.100/24 dev eth0 label eth0:vip
}
track_script {
check_haproxy
}
notify_master "/etc/keepalived/notify.sh master"
notify_backup "/etc/keepalived/notify.sh backup"
notify_fault "/etc/keepalived/notify.sh fault"
}
辅助脚本
HAProxy 进程检查脚本:
#!/bin/bash
# /etc/keepalived/check_haproxy.sh
# 检查 HAProxy 进程是否存活
if ! killall -0 haproxy 2>/dev/null; then
# 尝试自动拉起
systemctl start haproxy
sleep 2
if ! killall -0 haproxy 2>/dev/null; then
echo "HAProxy is DOWN" >&2
exit 1
fi
fi
exit 0
状态变更通知脚本:
#!/bin/bash
# /etc/keepalived/notify.sh
# VRRP 状态变更时发送告警
STATE=$1
HOSTNAME=$(hostname)
VIP="192.168.1.100"
TIME=$(date '+%Y-%m-%d %H:%M:%S')
case "$STATE" in
master)
MSG="⚠️ ${HOSTNAME} 已成为 VRRP MASTER,VIP ${VIP} 已漂移到本机 [${TIME}]"
;;
backup)
MSG="ℹ️ ${HOSTNAME} 已切换为 VRRP BACKUP,VIP ${VIP} 已释放 [${TIME}]"
;;
fault)
MSG="🔴 ${HOSTNAME} VRRP 进入 FAULT 状态,请检查网络和 HAProxy [${TIME}]"
;;
esac
# 发送到企业微信/钉钉/飞书 Webhook
curl -s -X POST 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY' \
-H 'Content-Type: application/json' \
-d "{\"msgtype\":\"text\",\"text\":{\"content\":\"${MSG}\"}}"
# 同时记录本地日志
logger -t keepalived "$MSG"
启动服务
# 两台均执行
chmod +x /etc/keepalived/check_haproxy.sh /etc/keepalived/notify.sh
chown root:root /etc/keepalived/*.sh
systemctl enable haproxy keepalived
systemctl start haproxy
systemctl start keepalived
# 验证 VIP
ip addr show eth0 | grep 192.168.1.100
常用负载均衡算法
| 算法 | HAProxy 配置 | 适用场景 |
|---|---|---|
| 轮询 | roundrobin |
后端性能均匀,无状态服务 |
| 最少连接 | leastconn |
长连接、请求耗时差异大 |
| 源地址哈希 | source |
需要会话保持但无 cookie |
| URI 哈希 | uri |
缓存命中率优化 |
| 带权重轮询 | roundrobin + weight N |
异构后端(CPU/内存不同) |
| 首个可用 | first |
连接池场景(先打满一台再分下一台) |
| 动态权重 | roundrobin + dynamic |
运行时通过 socket 调整 |
动态后端管理
HAProxy 的运行时 socket 是其核心优势之一——无需 reload 即可增删后端服务器:
# 连接 admin socket
socat /var/lib/haproxy/stats -
# 查看所有后端状态
show servers state
# 优雅下线一台服务器(标记为 DRAIN,等待连接排空)
set server web_servers/web1 state drain
# 彻底下线
set server web_servers/web1 state maint
# 上线恢复
set server web_servers/web1 state ready
# 运行时调整权重
set server web_servers/web1 weight 5
# 查看某后端服务器详情
show servers state web_servers
# 统计信息
show info
show stat
生产建议:封装上述命令为脚本,集成到 CI/CD 发布流程中——发布时先
drain→ 等待连接排空 → 部署新版本 →ready→ 逐台滚动。
健康检查策略
七层检查
# 基础 HTTP 检查
option httpchk GET /healthz
http-check expect status 200
# 高级:检查响应体
option httpchk GET /api/health
http-check expect string "ok"
# 检查响应头
http-check expect rheader "X-Service-Status: healthy"
# 连续 N 次成功才标记 UP,M 次失败才标记 DOWN
server web1 192.168.1.21:80 check inter 2000 rise 2 fall 3
# inter=检查间隔 rise=连续成功次数 fall=连续失败次数
四层检查
# TCP 连接检查
option tcp-check
tcp-check connect
# Redis 协议检查
tcp-check send PING\r\n
tcp-check expect string +PONG
# MySQL 协议检查(需要使用 mysql-check)
option mysql-check
mysql-check user haproxy_check
# 需在 MySQL 创建只读检查用户:CREATE USER 'haproxy_check'@'%' IDENTIFIED BY '';
被动健康检查
# 基于响应状态的被动检查
observe layer7
# 当后端返回 503/502 时自动降权
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
# 连续错误计数
default-server inter 2s rise 2 fall 3 observe layer7 on-error mark-down
双主模式(Active-Active)
标准主备模式浪费一半算力。利用 VRRP 双实例可实现双主——两台 HAProxy 同时承载流量,各持有一个 VIP:
# 主节点 keepalived.conf — 同时是 VI_1 的 Master 和 VI_2 的 Backup
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
virtual_ipaddress { 192.168.1.100/24 dev eth0 }
}
vrrp_instance VI_2 {
state BACKUP
interface eth0
virtual_router_id 52
priority 90
virtual_ipaddress { 192.168.1.101/24 dev eth0 }
}
# 备节点 keepalived.conf — 同时是 VI_1 的 Backup 和 VI_2 的 Master
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 90
virtual_ipaddress { 192.168.1.100/24 dev eth0 }
}
vrrp_instance VI_2 {
state MASTER
interface eth0
virtual_router_id 52
priority 100
virtual_ipaddress { 192.168.1.101/24 dev eth0 }
}
DNS 层面用轮询将 www.example.com 同时解析到 .100 和 .101,实现两台同时对外服务。任一宕机,另一个 VIP 自动漂移到存活节点。
监控与告警
Prometheus 指标采集
# HAProxy 2.x 原生导出 Prometheus 指标
# haproxy.cfg 中添加
listen prometheus
bind *:8405
mode http
http-request use-service prometheus-exporter if { path /metrics }
# prometheus.yml
scrape_configs:
- job_name: 'haproxy'
static_configs:
- targets: ['192.168.1.11:8405', '192.168.1.12:8405']
关键告警规则
# 告警规则示例
groups:
- name: haproxy
rules:
- alert: HaproxyBackendDown
expr: haproxy_backend_status != 1
for: 1m
labels: { severity: critical }
- alert: HaproxyBackendHighErrorRate
expr: rate(haproxy_backend_http_responses_total{code=~"5.."}[5m]) / rate(haproxy_backend_http_responses_total[5m]) > 0.05
for: 5m
labels: { severity: warning }
annotations:
summary: "后端 5xx 错误率超过 5%"
- alert: HaproxySessionQueueHigh
expr: haproxy_backend_current_queue > 100
for: 2m
labels: { severity: warning }
- alert: KeepalivedVRRPChanged
expr: changes(keepalived_vrrp_state[1m]) > 0
labels: { severity: critical }
annotations:
summary: "VRRP 状态发生切换"
故障切换验证
# 1. 正常状态:VIP 在 Master 上
# Master 节点
ip addr show eth0 | grep "192.168.1.100"
# → inet 192.168.1.100/24 scope secondary eth0
# 2. 模拟 HAProxy 故障
systemctl stop haproxy
# → check_haproxy.sh 尝试拉起失败 → weight -30 → 优先级降到 70 < Backup 90
# → Backup 抢占 VIP
# 3. 验证 VIP 漂移
# Backup 节点
ip addr show eth0 | grep "192.168.1.100"
# → inet 192.168.1.100/24 scope secondary eth0
# 4. 恢复 Master 的 HAProxy
systemctl start haproxy
# → check_haproxy.sh 成功 → 优先级恢复到 100 > Backup 90
# → preempt_delay 5 秒后 VIP 漂回 Master
# 5. 模拟网络分区(拔网线)
# Master: VRRP 广播超时 → Backup 在 3*advert_int 秒后超时接管
# 双方都认为自己是 Master → split-brain 风险!
# 解决方案见下方避坑清单
避坑清单
| # | 坑 | 症状 | 解决 |
|---|---|---|---|
| 1 | VRRP 脑裂 | 两个节点同时持有 VIP | 配置 vrrp_strict + 交叉监控脚本 + 多播/单播确认网络连通 |
| 2 | HAProxy reload 丢连接 | reload 时活跃连接中断 | 使用 haproxy -c 验证配置 + systemctl reload 平滑重载(fork 新进程接管 socket) |
| 3 | 健康检查端口不通 | 后端全部标红,502 风暴 | 确认 check 端口与后端监听端口一致;后端防火墙放行 HAProxy IP |
| 4 | Cookie 会话保持不生效 | 请求轮询到不同后端 | 确认 cookie SERVERID insert + 后端 cookie sN 标签配置正确 |
| 5 | SSL 证书更新不生效 | 证书过期后仍用旧证书 | HAProxy 不会自动重载证书,需 systemctl reload haproxy 触发重新读取 |
| 6 | 四层模式获取不到客户端 IP | 后端日志全是 HAProxy IP | 四层用 source + Proxy Protocol v2;七层用 option forwardfor |
| 7 | Keepalived 优先级计算错误 | Master 恢复后不抢回 VIP | weight -30 后优先级 = priority + weight = 70 < Backup 90 → Backup 继续持有,等 Master 脚本恢复后 100 > 90 才抢回 |
| 8 | slowstart 导致新后端过载 | 新加入的服务器瞬间接收大量流量 | slowstart 30s 让权重从 1 线性增长到设定值 |
| 9 | maxconn 设置不当 | HAProxy 内存溢出 OOM | maxconn 全局值 ≤ ulimit -n;后端 maxconn ≤ 服务实际承载能力 |
| 10 | VRRP 广播被防火墙拦截 | Backup 一直无法感知 Master 宕机 | 放行 VRRP 协议:iptables -A INPUT -p 112 -j ACCEPT |
总结
HAProxy + Keepalived 是 Linux 下负载均衡与高可用的黄金组合。核心要点:
- 四层用 HAProxy
mode tcp,七层用mode http,按场景选择 - Keepalived 的
track_script是灵魂——HAProxy 挂了 VIP 必须漂移,不能只靠进程守护 - 动态后端管理(socket)是滚发布的利器——
drain→ 部署 →ready,零停机 - 双主模式最大化资源利用率,两台同时服务,DNS 轮询分流
- 脑裂是最大风险——务必配置交叉监控 +
vrrp_strict+ 心跳网络冗余
生产环境进阶:如果 HAProxy 单机性能不够,可在 HAProxy 前面再加一层 LVS(DR 模式),LVS 负责四层分发到多台 HAProxy,HAProxy 负责七层路由——这就是经典的 LVS + HAProxy + Nginx 三层架构。

