为什么四层负载均衡仍然值得学
很多站长一提到负载均衡,脑子里蹦出来的第一反应是 Nginx 的 upstream。Nginx 确实好用,做七层反向代理、按 URL 分流、按 Host 分流都游刃有余。但 Nginx 本质上是一个应用层软件,它必须把整个 TCP 连接收下来、解析 HTTP 请求,再转发给后端。当你的业务是 TCP 长连接、数据库代理、游戏服务、或者单纯就是需要扛住几十万并发连接时,七层方案会先被自己拖死。
LVS(Linux Virtual Server)走的是另一条路。它是内核态的四层负载均衡,工作在 netfilter 框架里,靠 IPVS 模块直接改写数据包的目的地址,然后交给内核转发。它不解析应用层协议,不建立完整的用户态连接,因此吞吐量是 Nginx 的好几倍,单机扛住几十万并发连接是家常便饭。而 Keepalived 则负责给 LVS 提供高可用——两台调度器互相盯着,主挂了备自动接管那个对外的虚拟 IP。
本文从零讲清楚 LVS + Keepalived 的 DR 模式部署:为什么要用 DR 而不是 NAT、VIP 是怎么在两台机器间漂移的、后端为什么必须把 VIP 绑到 lo 接口、以及一个几乎人人都会踩的坑——ARP 广播导致整个集群流量错乱。
LVS 的三种工作模式
在动手之前必须先搞清楚 LVS 的三种转发模式,因为它们决定了网络拓扑和后端配置,选错了后面全是坑。
- NAT 模式:调度器改写请求包的目的 IP,转发给后端;后端处理完把响应交回调度器,调度器再改写源 IP 发回客户端。问题是所有响应流量都要经过调度器,调度器立刻成为新的瓶颈,而且后端必须把网关指向调度器。现在基本没人用了。
- TUN 模式:调度器把请求包用 IP 隧道封装后发给后端,后端解封装处理,响应直接回给客户端,不再经过调度器。解决了响应流量瓶颈,但要求后端内核支持 IPIP 隧道,且不支持端口映射,跨机房时也容易出现 MTU 问题。
- DR 模式(Direct Routing,直接路由):调度器只改写请求包的 MAC 地址,把包转发给后端;后端处理完后,用自己 lo 接口上绑定的 VIP 作为源地址,直接把响应发给客户端。响应流量完全绕开调度器,性能最好,是生产环境的主流选择。
本文的一切配置都基于 DR 模式,因为它是最常用也最能体现 LVS 价值的方案。
拓扑规划与 IP 分配
先把机器和地址定下来,后面所有配置都围绕这张表展开。假设你有两台调度器和两台后端:
角色 主机名 内网 IP 说明
调度器主 lb-master 10.0.0.11 绑定 VIP,对外服务
调度器备 lb-backup 10.0.0.12 待命,主挂了接管 VIP
后端 1 web-1 10.0.0.21 真实服务器 RS
后端 2 web-2 10.0.0.22 真实服务器 RS
虚拟 IP VIP 10.0.0.100 客户端访问的地址
关键约束:两台后端必须和调度器在同一个二层网络里(同一个交换机、同一个网段)。因为 DR 模式靠改 MAC 地址转发,二层到不了就没法玩。这是 DR 模式最硬的限制,跨网段、跨机房就不要用 DR,改用 TUN 或干脆上七层。
第一步:给调度器安装 IPVS 和 Keepalived
IPVS 早就合并进内核主线了,现代发行版的内核都自带这个模块,你只需要装管理工具和 Keepalived:
apt-get update
apt-get install -y ipvsadm keepalived
装完先确认内核模块可用:
ipvsadm --version
lsmod | grep ip_vs
如果 lsmod 没有输出也不用慌,模块是加载时才用的,Keepalived 启动时它会自动 modprobe。手动验证一下:
modprobe ip_vs
lsmod | grep ip_vs
第二步:配置 Keepalived 的 VRRP 高可用
Keepalived 的核心是 VRRP 协议。两台机器组成一个虚拟路由器组(VRID 相同),周期性互相发送心跳通告。主的优先级高,持有 VIP;主的挂了,几秒内备的通告超时,备立刻抢占 VIP。客户端对此毫无感知。
主调度器 /etc/keepalived/keepalived.conf:
global_defs {
router_id lb-master
enable_script_security
}
vrrp_script chk_haproxy {
script "/usr/bin/kill -0 $(cat /var/run/keepalived.pid)"
interval 2
weight -20
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass zz1984vip
}
virtual_ipaddress {
10.0.0.100/24 dev eth0 label eth0:vip
}
track_script {
chk_haproxy
}
}
备调度器的配置几乎一样,只改三处:router_id 改成 lb-backup、state 改成 BACKUP、priority 改成 90(必须比主低)。其它像 virtual_router_id、auth_pass、virtual_ipaddress 必须和主一模一样,否则组不起来。
几个容易忽略的点:
advert_int 1表示每秒发一次心跳,配合默认 3 倍超时,故障切换大概在 3 秒左右。对绝大多数业务够用,再快就要牺牲网络抖动下的稳定性。track_script做健康检查,如果本机的服务异常,weight -20会让本机优先级降下来,主动让出 VIP。这是避免"接管了 VIP 但服务是坏的"的关键。label eth0:vip给 VIP 起个别名,方便ip addr里一眼认出来。
启动并验证 VIP 是否落在主上面:
systemctl enable --now keepalived
ip addr show eth0 | grep 10.0.0.100
主上应该能看到 VIP 那个地址。此时把主停掉,几秒后再看备,VIP 应该漂移过去了。
第三步:配置 IPVS 负载均衡规则
Keepalived 不光管 VIP,它还能直接下发 IPVS 规则。在 keepalived.conf 末尾追加一个 virtual_server 块,Keepalived 会在本机自动维护 ipvsadm 规则:
virtual_server 10.0.0.100 80 {
delay_loop 6
lb_algo wrr
lb_kind DR
persistence_timeout 50
protocol TCP
real_server 10.0.0.21 80 {
weight 1
TCP_CHECK {
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
connect_port 80
}
}
real_server 10.0.0.22 80 {
weight 1
TCP_CHECK {
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
connect_port 80
}
}
}
逐项解释:
lb_algo wrr:加权轮询(Weighted Round Robin),按后端权重分配流量。权重高的机器接更多请求,适合新旧机器混布的过渡期。lb_kind DR:指定 DR 模式,这是和前面拓扑对应的关键参数。persistence_timeout 50:会话保持 50 秒。同一个客户端 IP 在 50 秒内的请求都发往同一台后端,对没有做会话共享的应用很重要。但如果后端已经用 Redis 共享了 session,这个值可以设成 0 关闭,让流量分配更均匀。TCP_CHECK:健康检查,连不上后端的 80 端口就把它踢出集群;恢复了自动加回来。
加载后查看规则是否生效:
ipvsadm -Ln
应该看到 VIP 下面挂着两个 real server,前面标着 Route(DR 模式)。
第四步:后端为什么要绑定 VIP 到 lo
这是 DR 模式最反直觉、也是新手最容易漏掉的一步。DR 模式下后端收到的是目的 MAC 是本机、但目的 IP 仍然是 VIP 的数据包。如果后端内核发现 VIP 不在自己的网卡上,它会直接丢掉这个包——因为 Linux 默认丢弃"目的地址不是本机"的包(这种行为由 arp_ignore 和 rp_filter 控制)。
所以后端必须把 VIP 绑到本地回环接口上,但——又不能真的对外宣告这个 IP,否则 ARP 广播会冲突。
正确的做法是在后端执行:
# 抑制 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
# 把 VIP 绑到 lo,注意掩码必须写 32 位,避免产生广播路由
ip addr add 10.0.0.100/32 dev lo label lo:vip
解释这两个内核参数:
arp_ignore=1:只有当 ARP 请求的目标 IP 正好落在收到请求的那个网卡的地址上时才回应。这样 lo 上的 VIP 不会响应来自 eth0 的 ARP 询问。arp_announce=2:发送 ARP 请求时,一律使用最合适的本地地址作为源地址,绝不拿 lo 上的 VIP 去宣告。
这两个参数是 DR 模式的命根子。如果漏了,两台后端的 VIP 会互相抢答 ARP,导致交换机 MAC 表被刷乱,客户端流量直接跑到后端去了,调度器形同虚设,现象就是"配置看起来全对,但流量分配完全乱套"。
把这些写进开机脚本,避免重启后失效。可以放进 /etc/rc.local,或者写一个 systemd oneshot 服务。这里给一个简单的 systemd 单元 /etc/systemd/system/lvs-rs.service:
[Unit]
Description=Configure LVS Real Server VIP on lo
After=network.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/lvs-rs-up.sh
[Install]
WantedBy=multi-user.target
对应的 /usr/local/bin/lvs-rs-up.sh 就是把上面那几行 echo 和 ip addr add 包起来,加个幂等判断:
#!/bin/bash
VIP=10.0.0.100
if ! ip addr show lo | grep -q "$VIP"; then
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 $VIP/32 dev lo label lo:vip
fi
五、验证与常见的三个坑
配置完成后按顺序验证:
- 客户端连续请求 VIP,观察后端访问日志是否两台都有记录,且比例接近权重。
- 把其中一台后端停掉,观察是否在健康检查周期内被踢出,请求全部转到另一台。
- 把主调度器停掉,观察 VIP 是否在 3 秒内漂移到备,且服务不中断。
三个高频坑位:
- 忘了改
arp_ignore/arp_announce,导致后端抢答 ARP。现象是流量乱窜,排查手段是arping看谁在应答 VIP,或者抓包看 ARP 回复来自哪台。 - 防火墙拦掉了 VRRP 协议。Keepalived 之间用的是 IP 协议号 112(不是 TCP/UDP 端口),iptables/nftables 里如果做了严格的协议白名单,必须放行
ip protocol vrrp,否则心跳收不到,直接双主(两边都持有 VIP,灾难现场)。 - 后端还把 VIP 的网关指向了别处。DR 模式下后端处理完响应直接用 lo 上的 VIP 作源地址回给客户端,不需要经过调度器,所以后端的默认网关保持正常网络配置即可,千万不要为了"对称"去改网关。
真实场景复盘:一次 ARP 引发的流量错乱
我曾经在一个客户的电商项目上遇到过典型案例。他们上线了 LVS + Keepalived,配置表看起来完全正确,ipvsadm 规则也在,但监控显示流量几乎全压在一台后端上,另一台几乎空转,而调度器的转发计数增长得又特别慢。
排查路径是这样的:先确认 ipvsadm 规则,正常;再抓调度器出口流量,发现转发量确实很小,说明大量请求根本没走调度器;最后用 arping -I eth0 10.0.0.100 探测 VIP,发现应答的 MAC 竟然是两台后端的网卡,而调度器的 MAC 根本不在应答列表里。根因就是后端漏配了 arp_announce,两台后端各自在宣告 VIP,交换机的 MAC 地址表被反复刷新,客户端 ARP 缓存里 VIP 直接映射到了后端的 MAC。
修复动作很简单:在后端补上那四条 arp 参数并重启网络,问题立刻消失。但代价是这次故障已经持续了三天,期间流量分配严重不均,其中一台后端长期过载。教训很明确——DR 模式里,ARP 参数的优先级高于一切应用层配置,它不出现在任何一行 load balancer 配置里,却是整个方案能否工作的地基。上线前的验收清单里,一定要有一项是"从客户端 arping VIP,确认只有调度器单方面应答"。
总结
LVS + Keepalived 的组合看起来比 Nginx 复杂,但它解决的是 Nginx 解决不了的问题:内核态四层转发带来的极致吞吐、几十万并发连接的承载力、以及不依赖应用层解析的极致效率。DR 模式是其中最值得掌握的方案——响应流量绕开调度器、性能损耗最小。
记住四个要点:调度器和后端必须同网段(二层可达)、VIP 只由 Keepalived 在调度器上宣告、后端把 VIP 绑到 lo 且必须收口 ARP、健康检查要覆盖到后端真实服务而不只是端口存活。把这四点做对,你的服务就能在单台后端故障时继续服务,在单台调度器宕机时几秒内自动接管——这才是真正意义上的高可用。