1. 从单点故障说起:为什么高可用架构不是可选项
很多团队在项目初期都经历过这样的阶段:一台服务器扛下所有流量,跑得好好的,直到某天凌晨磁盘满了、进程崩了、机房网络抖了一下,业务直接归零。这种“单点故障”带来的痛,只有真正被电话叫醒过的人才能体会。高可用(High Availability,简称HA)要解决的核心问题只有一个:当某个节点不可用时,服务能自动切换到备用节点,让用户几乎无感知。
围绕这个目标,业界沉淀出了几条主流技术路线。标题里提到的三个关键词——Keepalived热备、Keepalived+LVS、HAproxy服务器——其实代表了三种不同层次、不同侧重点的高可用方案。它们不是互相替代的关系,而是经常组合使用,分别解决“谁来做主”“流量怎么分发”“请求怎么转发”这三个问题。
Keepalived热备解决的是节点级冗余:两台机器一主一备,主挂了备顶上,靠的是VRRP协议和虚拟IP漂移。Keepalived+LVS解决的是四层负载均衡下的高可用:LVS负责把流量分给后端真实服务器,Keepalived负责让LVS调度器本身不成为单点。HAproxy则工作在七层,能基于HTTP头部、URL、Cookie做更精细的转发和健康检查,同样可以用Keepalived做高可用。
这篇文章适合谁看?如果你正在搭建一套需要7×24小时稳定运行的服务,或者你已经在用Nginx做反向代理但发现它扛不住某些场景,又或者你只是想把“高可用”这三个字从概念变成能落地的配置,那接下来的内容会一步步把原理、选型、配置和踩坑经验讲清楚。我会尽量用生活化的类比解释协议细节,同时给出可以直接抄的配置片段和排查命令。
2. Keepalived热备:VRRP协议与虚拟IP漂移的底层逻辑
2.1 VRRP到底在干什么:一个“虚拟路由器”的选举游戏
Keepalived的核心是VRRP(Virtual Router Redundancy Protocol,虚拟路由器冗余协议)。你可以把它想象成一个小区里只有一部对外公开的“总机号码”,但背后其实有两部电话机。平时由主电话机接听,主电话机坏了,备用电话机立刻接管这个号码,外面打电话的人根本不知道换了机器。
这个“总机号码”就是虚拟IP(VIP)。VRRP把一组服务器组成一个虚拟路由器,这个虚拟路由器有一个VIP和一个虚拟MAC地址。组内的服务器通过优先级(priority)选举出Master,Master持有VIP并响应ARP请求,Backup则处于监听状态。Master定期发送VRRP通告报文(默认1秒一次),Backup如果在3倍通告间隔内没收到通告,就认为Master挂了,触发新一轮选举。
这里有几个关键参数必须理解透:
- virtual_router_id:同一个VRRP组内必须一致,不同组必须不同,范围1-255。如果两台不相关的Keepalived配了相同的router_id且在同一网段,会互相抢VIP,这是新手最容易踩的坑之一。
- priority:优先级,数值越大越优先,范围1-254。Master通常配100以上,Backup配100以下。注意255是保留给IP地址拥有者的。
- advert_int:通告间隔,默认1秒。调小能加快故障检测,但会增加网络开销;调大则相反。
- state:初始状态,MASTER或BACKUP。但实际角色由优先级和选举决定,state只是启动时的初始声明。
- authentication:认证方式,推荐用PASS,同一组内密码必须一致。
2.2 抢占模式与非抢占模式:什么时候该让Master“抢回来”
Keepalived默认是抢占模式(preempt):当原来的Master恢复后,如果它的优先级仍然最高,它会重新抢回VIP。这在某些场景下是好事,比如主节点性能更强;但在另一些场景下是灾难,比如主节点刚重启,服务还没完全就绪就抢回流量,导致请求失败。
非抢占模式通过nopreempt参数开启,但有个硬性条件:初始state必须设为BACKUP,且优先级可以比另一台高。这样即使高优先级节点恢复,也不会主动抢回VIP,直到当前Master主动释放。我个人的经验是,数据库主库、有状态服务、启动慢的应用,一律用非抢占;无状态的前端服务可以用抢占,让性能好的机器多扛流量。
2.3 一个能直接用的Keepalived热备配置
假设有两台服务器,IP分别是192.168.1.10和192.168.1.11,VIP是192.168.1.100。主节点配置:
# /etc/keepalived/keepalived.conf global_defs { router_id node_master enable_script_security script_user root } vrrp_script chk_nginx { script "/usr/bin/killall -0 nginx" interval 2 weight -20 fall 2 rise 2 } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass MyPass123 } virtual_ipaddress { 192.168.1.100/24 dev eth0 label eth0:1 } track_script { chk_nginx } notify_master "/etc/keepalived/notify.sh master" notify_backup "/etc/keepalived/notify.sh backup" notify_fault "/etc/keepalived/notify.sh fault" }备节点只需改三处:router_id改成 node_backup,state改成 BACKUP,priority改成 90。virtual_router_id、auth_pass、virtual_ipaddress必须完全一致。
vrrp_script这段是精髓:它每2秒检查一次nginx进程是否存在,如果连续2次检查失败(fall 2),就把优先级减20(weight -20)。主节点优先级从100降到80,低于备节点的90,于是触发切换。这比单纯靠VRRP心跳检测更可靠,因为有时候机器没宕机,但服务已经挂了。
注意:
killall -0 nginx只是检查进程是否存在,不检查服务是否真正可用。更严谨的做法是用curl -sf http://127.0.0.1/health检查健康接口。
2.4 脑裂:最危险也最容易被忽视的问题
脑裂(split-brain)是指两台节点都认为自己是Master,同时持有VIP。结果就是VIP在两个MAC地址之间来回漂移,或者ARP表混乱,流量时通时断。造成脑裂的常见原因有三个:心跳线断了但服务器还活着、防火墙拦截了VRRP组播、virtual_router_id冲突。
排查脑裂的第一步是看日志:journalctl -u keepalived -f或/var/log/messages。如果看到两台都在发“Entering MASTER STATE”,基本可以确认。解决方案包括:用独立的心跳线(不要和业务网卡混用)、在交换机上放行VRRP协议(协议号112)、配置unicast_peer指定单播心跳而不是依赖组播。
我踩过最坑的一次是云环境里安全组默认拦截了组播,Keepalived根本收不到对方通告,两台都成了Master。后来改成单播模式才解决:
unicast_src_ip 192.168.1.10 unicast_peer { 192.168.1.11 }3. Keepalived+LVS:四层负载均衡的高可用组合拳
3.1 LVS的三种转发模式:NAT、DR、TUN怎么选
LVS(Linux Virtual Server)是内核级的四层负载均衡器,性能极高,因为它在内核空间处理数据包,不需要把请求交给用户态进程。它有三种工作模式:
| 模式 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| NAT | 调度器改写目标IP,请求和响应都经过调度器 | 配置简单,后端可跨网段 | 调度器成为带宽瓶颈 | 小规模、后端数量少 |
| DR(直接路由) | 调度器只改MAC地址,响应由后端直接回给客户端 | 性能最高,调度器只处理入站 | 后端必须和调度器同网段,需配置VIP在lo上 | 大多数生产环境首选 |
| TUN(隧道) | 用IP隧道封装请求转发给后端 | 后端可跨机房 | 需要支持隧道协议,配置复杂 | 跨地域分发 |
生产环境里DR模式用得最多。它的巧妙之处在于:调度器收到请求后,只把目标MAC改成某台真实服务器的MAC,IP层完全不动。真实服务器收到包后,发现目标IP是自己的VIP(配置在lo接口上),就处理请求,然后直接把响应发给客户端,不再经过调度器。这样调度器只承担入站流量,出站流量由后端自己扛,吞吐量大幅提升。
3.2 为什么LVS需要Keepalived:调度器本身不能是单点
LVS再强,如果只有一台调度器,它挂了整个集群就没了。所以标准做法是两台LVS调度器 + Keepalived做高可用。两台调度器组成VRRP组,同时持有VIP,只有Master对外服务。后端真实服务器通过ipvsadm配置在调度器上。
这里有个细节:后端服务器的VIP配置。在DR模式下,每台真实服务器都需要在lo接口上配置VIP,但必须关闭ARP响应,否则它们会响应客户端对VIP的ARP请求,导致流量绕过调度器直接打到后端。配置方法:
# 在每台真实服务器上执行 echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce ip addr add 192.168.1.100/32 dev lo label lo:0arp_ignore=1表示只回答目标IP是本机物理接口IP的ARP请求;arp_announce=2表示总是使用最佳本地地址发送ARP。这两个参数配合,确保后端不会“抢答”VIP的ARP。
3.3 调度算法:轮询、加权、最少连接的实际效果差异
LVS支持十几种调度算法,常用的有:
- rr(轮询):请求依次分给每台后端,适合后端性能一致的场景。
- wrr(加权轮询):按权重分配,性能强的机器权重高。权重需要根据CPU、内存、实际压测结果来定,不能拍脑袋。
- lc(最少连接):把请求发给当前连接数最少的后端,适合请求处理时间差异大的场景。
- wlc(加权最少连接):默认算法,综合了权重和连接数,大多数场景下表现最稳。
- sh(源地址哈希):同一客户端IP总是打到同一后端,适合需要会话保持的场景,但后端故障时会话会丢失。
我的一般建议是:无状态服务用wlc,有状态但能容忍会话丢失的用wlc,必须会话保持的用sh并配合后端会话共享。不要盲目用rr,因为实际后端性能往往不一致,rr会导致慢的机器堆积请求。
3.4 完整配置:Keepalived+LVS DR模式实战
主调度器Keepalived配置:
virtual_server 192.168.1.100 80 { delay_loop 6 lb_algo wlc lb_kind DR persistence_timeout 50 protocol TCP real_server 192.168.1.20 80 { weight 100 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 connect_port 80 } } real_server 192.168.1.21 80 { weight 100 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 connect_port 80 } } }persistence_timeout 50表示同一客户端50秒内的请求都打到同一后端,这是一种简单的会话保持。TCP_CHECK会定期探测后端80端口,连续失败3次就把该后端从池子里摘掉。
查看LVS转发状态用ipvsadm -Ln,输出里会显示每个后端的活跃连接数和权重。如果发现某个后端一直是0连接,要么是健康检查没通过,要么是权重配错了。
实操心得:DR模式下,后端服务器的默认网关不需要指向调度器,因为响应是直接回给客户端的。但如果你发现响应回不去,检查后端服务器的路由表,确保回包路径正确。
4. HAproxy:七层代理的健康检查与高可用实践
4.1 HAproxy和LVS的本质区别:四层与七层的分界线
LVS工作在四层(传输层),它只看IP和端口,不关心内容是HTTP还是MySQL。HAproxy工作在七层(应用层),它能解析HTTP协议,根据URL、Host头、Cookie做转发决策,还能做SSL终止、压缩、重写。
打个比方:LVS像一个邮局分拣员,只看信封上的地址把信分到不同区域;HAproxy像一个前台接待,能拆开信封看内容,根据信里写的事情类型转给不同的部门。所以HAproxy更灵活但性能略低,LVS更高性能但功能单一。实际架构中经常是:LVS做第一层四层负载均衡,后面挂多个HAproxy做七层转发,HAproxy后面才是真正的应用服务器。
4.2 健康检查的三种级别:从端口探测到业务级检测
HAproxy的健康检查比LVS精细得多,分三个层次:
- TCP检查:只检查端口是否能建立连接。配置
option tcp-check。 - HTTP检查:发送HTTP请求,检查返回状态码。配置
option httpchk GET /health。 - 业务级检查:检查返回内容里是否包含特定字符串。配置
http-check expect string "OK"。
我强烈建议至少用HTTP检查,因为端口开着不代表服务正常。曾经遇到过Tomcat进程还在但线程池满了,端口能连上但请求全部超时,TCP检查完全发现不了。加上http-check expect status 200后,这种假死状态就能被及时摘除。
4.3 HAproxy配置文件的结构与关键参数
HAproxy配置分四段:global、defaults、frontend、backend。下面是一个生产可用的模板:
global log /dev/log local0 maxconn 50000 user haproxy group haproxy daemon stats socket /var/run/haproxy.sock mode 600 level admin defaults log global mode http option httplog option dontlognull option redispatch retries 3 timeout connect 5s timeout client 30s timeout server 30s timeout check 3s frontend web_front bind *:80 bind *:443 ssl crt /etc/haproxy/certs/site.pem default_backend web_servers backend web_servers balance roundrobin option httpchk GET /health http-check expect status 200 server web1 192.168.1.20:8080 check inter 2s fall 3 rise 2 server web2 192.168.1.21:8080 check inter 2s fall 3 rise 2 backup几个关键参数解释:option redispatch表示如果某台后端挂了,重试时把请求发给其他健康后端;retries 3是重试次数;inter 2s fall 3 rise 2表示每2秒检查一次,连续3次失败标记为down,连续2次成功标记为up;backup标记该服务器为备用,只有主服务器全挂才启用。
4.4 用Keepalived给HAproxy做高可用
HAproxy本身不做高可用,需要Keepalived配合。配置和第二节的Keepalived热备几乎一样,只是健康检查脚本改成检查HAproxy进程:
vrrp_script chk_haproxy { script "killall -0 haproxy" interval 2 weight -30 fall 3 rise 2 }两台HAproxy都配置相同的backend,VIP在Master上。客户端访问VIP,Master上的HAproxy处理请求。Master挂了,VIP漂到Backup,Backup上的HAproxy接管。由于两台HAproxy配置完全一致,切换后行为不变。
注意:HAproxy的
stats socket可以用来做运行时管理,比如动态调整后端权重、下线某台服务器。配合socat工具可以脚本化操作,这在紧急故障时非常有用。
5. 三种方案的选型对比与组合架构
5.1 一张表看清三种方案的适用边界
| 维度 | Keepalived热备 | Keepalived+LVS | HAproxy |
|---|---|---|---|
| 工作层次 | 节点级 | 四层 | 七层 |
| 核心功能 | VIP漂移、节点冗余 | 四层负载均衡+高可用 | 七层转发+健康检查 |
| 性能 | 不涉及转发,无损耗 | 内核级,极高 | 用户态,较高 |
| 灵活性 | 低 | 低 | 高 |
| 配置复杂度 | 低 | 中 | 中 |
| 典型场景 | 数据库主备、单服务双机 | 大流量四层分发 | HTTP路由、SSL终止、灰度发布 |
| 会话保持 | 不涉及 | 源地址哈希 | Cookie插入/源地址哈希 |
选型的核心判断逻辑是:先问需不需要七层功能,再问流量规模有多大。如果只需要把请求分给几台后端且不需要看HTTP内容,LVS+Keepalived是性能和稳定性的最优解。如果需要根据URL路由、做SSL终止、或者后端是HTTP服务且需要精细健康检查,HAproxy更合适。如果只是两台机器做主备(比如MySQL主从),Keepalived热备就够了。
5.2 大规模场景下的组合架构:LVS+HAproxy+Keepalived
在日活百万级以上的场景里,常见架构是三层:
- 最外层:两台LVS调度器 + Keepalived,持有VIP,用DR模式把流量分给下层HAproxy集群。
- 中间层:多台HAproxy,每台都用Keepalived做双机热备(或者用ECMP等价路由做集群),负责七层转发和健康检查。
- 最内层:应用服务器集群,注册在HAproxy的backend里。
这样LVS扛住了四层的高并发,HAproxy提供了七层的灵活性,Keepalived保证了每一层都没有单点。每一层的健康检查独立工作:LVS检查HAproxy的端口,HAproxy检查应用的健康接口,任何一层出问题都能自动隔离。
这个架构的复杂度在于故障排查链路变长。请求不通时,要逐层确认:VIP在不在、LVS转发规则对不对、HAproxy后端状态是否健康、应用本身是否正常。我的习惯是每层都配好日志和监控,LVS用ipvsadm -Ln --stats看转发计数,HAproxy用stats页面看后端状态,应用层看自己的健康接口。
5.3 云环境下的特殊考量
在云平台上,VIP漂移需要平台支持。有些云厂商允许在控制台申请虚拟IP并绑定到实例,有些则限制ARP响应,导致Keepalived默认的组播模式无法工作。这时候要么改用单播模式,要么使用云厂商提供的负载均衡服务。
另外,云环境的网络延迟和丢包率与物理机房不同,VRRP的advert_int可能需要调大,避免网络抖动导致误切换。我一般会在云环境把advert_int设为2秒,fall设为3次,这样需要6秒才判定故障,虽然切换慢一点,但避免了频繁误切。
6. 实战排查:那些配置正确但就是不工作的时刻
6.1 VIP漂移了但服务不通:ARP缓存惹的祸
有一次切换后,VIP确实漂到了备节点,ip addr能看到VIP,但客户端访问就是不通。排查了半天发现是客户端和交换机的ARP缓存还指向旧Master的MAC。VRRP切换时会发送免费ARP(gratuitous ARP)通知大家更新缓存,但如果网络设备忽略了免费ARP,缓存就不会更新。
解决办法有两个:一是在Keepalived配置里加garp_master_delay和garp_master_repeat参数,多发送几次免费ARP;二是在切换脚本里手动发送ARP更新:
arping -I eth0 -c 3 -A 192.168.1.100-A表示发送ARP应答而不是请求,-c 3表示发3次。这个命令放在notify_master脚本里,能大幅提高切换后立即恢复的概率。
6.2 HAproxy后端全部显示DOWN但服务明明正常
这种情况通常是健康检查配置和后端实际行为不匹配。比如后端返回的是302重定向,但健康检查期望200;或者后端要求Host头,但健康检查没带。排查步骤:
- 在HAproxy服务器上手动执行健康检查请求:
curl -v http://后端IP:端口/health,看返回什么。 - 检查HAproxy日志:
tail -f /var/log/haproxy.log,看健康检查的具体失败原因。 - 用stats socket查看后端详情:
echo "show stat" | socat stdio /var/run/haproxy.sock。
常见修复方式:加http-check expect status 200 302允许多个状态码,或者在option httpchk里指定完整的请求头和Host。
6.3 LVS转发正常但后端收不到请求
DR模式下,如果后端收不到请求,先检查后端lo接口上的VIP是否配置正确,以及ARP参数是否生效。用ip addr show lo确认VIP在,用sysctl net.ipv4.conf.all.arp_ignore确认值是1。还有一个容易忽略的点:后端服务器的防火墙可能拦截了目标IP为VIP的包。因为包的目标IP是VIP,而VIP配置在lo上,某些防火墙规则会把它当作非法流量丢弃。检查iptables -L -n或firewall-cmd --list-all,确保没有拦截。
6.4 Keepalived日志里反复出现“VRRP_IS_INITIALIZED”错误
这个错误通常表示VRRP实例初始化失败,最常见的原因是virtual_router_id冲突或者网卡名称写错。用ip link show确认网卡名,用tcpdump -i eth0 vrrp抓包看是否有其他VRRP通告。如果同一网段有多个Keepalived组,确保每组的router_id不同。
7. 监控与日常维护:让高可用真正“可用”
7.1 必须监控的五个指标
高可用架构搭好只是开始,没有监控的高可用等于没有高可用。以下五个指标必须纳入监控:
- VIP归属:当前VIP在哪个节点上,是否与预期一致。可以用脚本定期检查
ip addr | grep VIP并上报。 - VRRP状态:Keepalived实例是MASTER还是BACKUP,状态切换次数。
- 后端健康状态:HAproxy或LVS里有多少后端处于down状态。
- 切换事件:每次VIP漂移都要记录并告警,因为非预期的切换意味着有故障发生。
- 连接数:LVS的活跃连接数、HAproxy的当前会话数,突增或突降都可能是异常信号。
7.2 定期演练:不要等真出事才发现切换不了
我见过太多团队配了Keepalived但从来没测试过切换,真到主节点挂了才发现备节点配置有误、VIP漂不过去。建议每季度至少做一次切换演练:手动停掉主节点的Keepalived,观察VIP是否在预期时间内漂到备节点,业务是否自动恢复,切换期间有多少请求失败。
演练时要注意:选择业务低峰期,提前通知相关方,准备好回滚方案。切换后检查所有后端服务的会话是否丢失、数据库连接是否正常。演练结束后分析切换耗时,如果超过预期,检查advert_int、fall等参数是否合理。
7.3 配置版本管理:别让两台机器的配置不一致
Keepalived和HAproxy的配置文件必须纳入版本管理(如Git),每次修改都提交并记录原因。两台机器的配置差异应该只有state、priority、router_id这几项,其他必须完全一致。我习惯用Ansible或类似的配置管理工具批量下发,避免手动修改导致不一致。
实操心得:在
notify_master、notify_backup脚本里加上配置校验逻辑,比如检查关键进程是否在运行、关键端口是否监听。如果校验失败,脚本返回非零值,Keepalived会认为切换失败并记录日志,避免切到一个不健康的节点上。
8. 我在这套架构上踩过的三个真实坑
第一个坑是优先级设置反了。主节点priority配了90,备节点配了100,结果备节点一直抢着当Master,主节点反而成了Backup。当时没仔细看日志,排查了半天才发现是数字大小搞反了。记住:priority越大越优先。
第二个坑是健康检查脚本权限问题。Keepalived默认以keepalived用户运行脚本,但脚本里执行的命令需要root权限,导致检查一直失败。后来在global_defs里加了script_user root和enable_script_security才解决。但要注意,用root跑脚本有安全风险,脚本内容必须严格可控。
第三个坑是HAproxy的timeout设置过短。后端某个接口处理时间较长(比如导出报表要10秒),但timeout server设了5秒,导致请求被HAproxy主动断开,用户看到502。后来把超时调到30秒,并对长耗时接口单独配置了更长的超时。这个问题的教训是:超时时间要根据业务实际响应时间来定,不能拍脑袋。
这套高可用架构从单机到双机、从四层到七层,每一步都是在解决具体问题的过程中逐步演进的。没有银弹,只有适合当前场景的组合。Keepalived负责“谁活着”,LVS负责“分得快”,HAproxy负责“分得准”,三者各司其职,配合好了才能让服务真正稳定运行。