做负载均衡这一块,LVS 的 DR 模式(Direct Routing)绝对是最绕不开的经典方案之一。很多人翻遍了网上的帖子,要么只给了命令没有原理,要么把数据流转讲得玄之又玄。这篇文章我尽量用大白话,把 LVS DR 模式从原理到配置、从选型到排障,一次讲透。不论你是刚接触负载均衡的运维实习生,还是准备优化公司现有集群的架构师,只要你需要处理高并发流量分发,这篇内容都值得你花十分钟仔细看完。
1. DR 模式到底解决了什么问题
1.1 三种模式一句话对比
LVS 官方提供三种工作模式,很多人一开始就卡在选型上。其实拿一句话就能说明白:
- NAT 模式:请求和响应都走调度器,调度器像公司前台,所有信都要经手,简单但最容易成为瓶颈;
- DR 模式(Direct Routing):请求进调度器,响应直接从后端服务器回给客户端,调度器只负责分发,性能最好,生产环境里用得最多;
- TUN 模式:把原始报文封装成隧道报文发给后端,RS 解封装后处理,适合跨机房跨网段的场景,但封装解封装有额外开销。
DR 模式之所以被广泛应用,核心就一个字:省。省掉了一半流量的转发压力,调度器只需要处理入站请求,不用扛响应流量。在高并发场景下,响应流量往往比请求流量大得多,这一点直接决定了 DR 模式可以扛住很大的吞吐量。
1.2 一个包从客户端到 RS 再到客户端,完整走一遍
要理解 DR 模式,关键是把数据包流转过程看一遍。
假设客户端 IP 是 203.0.113.5,VIP(虚拟 IP)是 192.168.1.100,后端 RS 是 192.168.1.11 和 192.168.1.12,调度器 Director 的物理 IP 是 192.168.1.10。
第一步,客户端发起访问,目的 IP 是 VIP 192.168.1.100。这个包正常走路由到达交换机,然后交换机广播 ARP 请求,问“谁有 192.168.1.100 这个地址”。整个局域网里只有调度器 Director 会回答这个 ARP 请求(后端 RS 的 ARP 被专门抑制了),所以交换机学到的是 VIP 对应 Director 的 MAC 地址。数据包最终到达 Director 的物理网卡。
第二步,Director 收到包后,根据负载均衡算法选出一台 RS,比如 192.168.1.11。注意关键操作来了:Director 会改写数据链路层的目标 MAC 地址,把目标 MAC 改成 192.168.1.11 的网卡 MAC,然后把数据包重新从网卡发出去。IP 层的源 IP 和目标 IP 完全不动,依旧是客户端的 IP 和 VIP。这就是“直接路由”名字的由来,它只动 MAC,不动 IP。
第三步,交换机根据 MAC 地址把数据包送到 192.168.1.11 这台 RS 上。RS 发现数据包目标 IP 是 VIP 192.168.1.100,而它自己的 lo:0 接口上恰好绑定了这个 VIP,于是认为这个包确实是给自己的,正常收下,交给 nginx 或者 Tomcat 之类的 Web 服务处理。
第四步,RS 处理完请求后,直接构造响应数据包,源 IP 是 VIP 192.168.1.100,目标 IP 是客户端 203.0.113.5。它不把响应回传给 Director,而是按自己机器的路由表走,从网卡发给交换机,交换机再一路转回客户端。客户端看到的响应源 IP 就是 VIP,完全感知不到后端是哪台机器处理的。
这个流程里最核心的一句话就是:进站流量只改 MAC,出站流量完全绕开调度器。这也是 DR 模式高性能的根本原因。
1.3 为什么必须绑定 VIP 和压制 ARP
理解了数据流转,你会发现一个隐藏问题:RS 收到目标 IP 是 VIP 的包,凭什么认为这是自己的包?如果 RS 压根没绑定 VIP,内核会直接把这个包丢掉,因为本机没有哪个接口配置了 192.168.1.100 这个地址。
所以在每台 RS 上,都要把 VIP 配置到 loopback 接口上,通常是 lo:0,掩码必须写成 255.255.255.255(也就是 /32)。为什么要弄在 loopback 上?因为把 VIP 放在 eth0 这种物理接口上,很容易和实际 IP 互相干扰,而且掩码广播可能产生额外的 ARP 响应。放在 lo 上,单纯让内核认这个地址,不占物理接口。
但问题又来了:如果每台 RS 的 lo 上都绑定了同一个 VIP,当交换机广播“谁有 192.168.1.100”时,所有 RS 默认都会回答“我有”。这样 ARP 缓存就乱了,交换机一会儿把包发给这台机器的 MAC,一会儿发给那台,整个集群直接抽风。所以必须设置两个核心内核参数:
- arp_ignore = 1:只回答目标 IP 是本接口 IP 的 ARP 请求。RS 的 eth0 接口 IP 是 192.168.1.11,当收到“谁有 192.168.1.100”的请求时,目标 IP 不是 eth0 的地址,于是不回应。
- arp_announce = 2:发起 ARP 请求时,尽量使用最合适的本机地址作为源 IP,避免把 VIP 这种虚拟地址作为 ARP 包的源地址广播出去。
这两个参数缺一不可,只配 arp_ignore 不配 arp_announce 也勉强能跑,但生产环境一定要把两个都配上,而且要持久化,不然机器一重启就前功尽弃。
2. 动手前的环境与设计,别急着敲命令
2.1 网络拓扑标准形态
DR 模式的一个硬性前提是:Director 和所有 RS 必须在同一个二层网络里。为什么?因为 Director 转发数据包时只改目标 MAC 地址,不改 IP,这个包必须在同一个广播域内才能送到 RS。如果 RS 在另一个网段,交换机一看目标 IP 根本不是本网段的,直接就按路由走了,Director 改的 MAC 地址毫无意义。
典型拓扑是这样:一台或者两台 Director(配合 keepalived 做高可用),下面挂 N 台 RS,所有机器接在同一台交换机或者同一个 VLAN 内,VIP 配置在 Director 的网卡上(通常是一块独立网卡或子接口),后端 RS 的 lo:0 上也绑定 VIP,但 ARP 被抑制。
有一点要提前说清楚:RS 的默认网关一定要指向上游路由器或交换机,而不是指向 Director。很多人在这里被坑过,RS 的网关写了 Director 的 IP,结果响应包回传又绕回 Director,等于把 DR 模式硬生生改成了低配版 NAT,而且乱七八糟的转发路径让排查变成噩梦。
2.2 角色分工与系统准备
按照一个标准集群来规划,假设我有两台 Director 做高可用,三台 RS 跑 Web 服务:
| 角色 | 主机名 | IP 地址 | VIP | 说明 |
|---|---|---|---|---|
| Director-Master | lvs-master | 192.168.1.10 | 192.168.1.100 | keepalived MASTER |
| Director-Backup | lvs-backup | 192.168.1.20 | 192.168.1.100 | keepalived BACKUP |
| RealServer-1 | rs-1 | 192.168.1.11 | 192.168.1.100 (lo:0) | nginx |
| RealServer-2 | rs-2 | 192.168.1.12 | 192.168.1.100 (lo:0) | nginx |
| RealServer-3 | rs-3 | 192.168.1.13 | 192.168.1.100 (lo:0) | nginx |
软件层面,Director 需要安装 ipvsadm 用来配置 LVS 规则,另外再装 keepalived 实现 VIP 漂移和健康检查。RS 只需要正常的 Web 服务环境,不需要安装 LVS 相关的任何东西,只需要改内核参数和绑 VIP。
系统我建议直接用 CentOS/Rocky 这类发行版,内核自带 ip_vs 模块,不需要额外编译。Ubuntu/Debian 也没问题,配置方法完全一致。
2.3 三个必须提前确认的内核开关
在动手之前,有几件事一定要确认,不然后面全是坑。
第一件:确认 ip_vs 模块可用。运行modprobe ip_vs,然后lsmod | grep ip_vs,如果没有输出,说明内核没加载成功,可能要检查内核版本或者安装对应包。这一步不通过,后续 ipvsadm 全白搭。
第二件:DR 模式不需要开启 IP 转发。NAT 模式才需要net.ipv4.ip_forward=1,DR 模式改了 MAC 直接在同一二层网段转发,根本不需要路由转发。网上很多教程一刀切地让你开 IP 转发,DR 模式下开不开都行,但没必要,建议保持默认值,减少攻击面。
第三件:RS 侧的 rp_filter(反向路径过滤)最好设为宽松模式或者关闭,具体做法是:
echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter echo 0 > /proc/sys/net/ipv4/conf/eth0/rp_filter为什么要动它?rp_filter 的作用是检查进入的数据包源 IP 是否可从收到包的接口返回,如果不对称就丢弃。DR 模式下 RS 收到的包源 IP 是客户端地址,目标 IP 是 VIP,包从 eth0 进来,但本机对客户端地址的回应路径可能不完全符合 rp_filter 的校验逻辑,在个别内核版本和网络环境下会产生丢包。提前把它关了,能省掉后面很多莫名的连接超时问题。
3. 实操配置,按这个顺序来就行
3.1 RS 侧三步配置:绑定 VIP、抑制 ARP、持久化
RS 侧的配置是整个 DR 模式最容易出错的地方,也是最关键的环节。我给出一套完整步骤,每台 RS 都要执行一遍。
第一步,绑定 VIP 到 lo:0:
ip addr add 192.168.1.100/32 dev lo:0注意这里掩码是 /32,这个细节很关键。如果你写成 /24,即使其他配置都对,也容易出现 ARP 响应异常。
第二步,立即生效 ARP 抑制参数:
echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore echo 1 > /proc/sys/net/ipv4/conf/eth0/arp_ignore echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce echo 2 > /proc/sys/net/ipv4/conf/eth0/arp_announce很多人只写了 all 的配置,实际内核在判断 arp_ignore 时取的是 max(conf/all, conf/eth0) 这类取大值逻辑,而 arp_announce 取的是 min 逻辑。理论上只配 all 也行,但为了防止云平台、虚拟化层对参数进行干扰,我习惯把所有实际生效的接口都配一遍,图个稳妥。
第三步,写入 /etc/sysctl.conf 持久化:
net.ipv4.conf.all.arp_ignore = 1 net.ipv4.conf.all.arp_announce = 2 net.ipv4.conf.lo.arp_ignore = 1 net.ipv4.conf.lo.arp_announce = 2 net.ipv4.conf.eth0.arp_ignore = 1 net.ipv4.conf.eth0.arp_announce = 2 net.ipv4.conf.all.rp_filter = 0 net.ipv4.conf.eth0.rp_filter = 0改完之后运行sysctl -p让配置生效,再用sysctl -a | grep arp_ignore确认你期望的值都写进去了。这里有个容易迷糊的地方:/proc/sys/net/ipv4/conf/all/下的值不是单独限制某个网卡,它是一个全局模板,最终每个接口的生效值由接口自己的配置和 all 组合决定。所以当你发现 eth0 上的值不对时,别惊讶,检查 all 的配置和 eth0 自己的配置是否一致。
另外,RS 上 web 服务绑定的地址要注意。很多 nginx 配置成监听某个具体物理 IP,比如listen 192.168.1.11:80,这就糟糕了,因为请求目标 IP 是 VIP,nginx 根本不监听 VIP。正确做法要么监听0.0.0.0:80,要么干脆监听192.168.1.100:80。我推荐统一监听 0.0.0.0,后面换 VIP 时不用一台一台改 nginx 配置。
3.2 LVS 调度器配置:ipvsadm 命令实战
接下来配置 Director。假设我已经在 Director 上装了 ipvsadm。
第一步,加载模块并确认:
modprobe ip_vs modprobe ip_vs_wrr modprobe ip_vs_sh lsmod | grep ip_vs第二步,在 Director 的 eth0 上绑定 VIP:
ip addr add 192.168.1.100/24 dev eth0有的人习惯用eth0:0子接口,本质上没区别。只要 Director 的这个接口能响应 VIP 的 ARP 请求即可。注意这里掩码用的是 /24,需要让 Director 认为 VIP 和自己同网段,能正常对外通信,和 RS 的 /32 是两个概念。
第三步,配置 LVS 虚拟服务:
ipvsadm -A -t 192.168.1.100:80 -s wrr -p 300 ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.11:80 -g -w 2 ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.12:80 -g -w 1 ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.13:80 -g -w 1解释一下每个参数:
-A表示添加一个虚拟服务,-t指定协议为 TCP 和 VIP 端口;-s wrr指定调度算法为加权轮询;-p 300表示持久连接超时时间 300 秒,来自同一客户端的连接在这个时间内都会转发到同一台 RS,可以理解为一种会话保持;-a表示往虚拟服务里添加 RS,-r指定 RS 地址和端口;-g是关键,它表示使用网关模式,也就是 DR 模式,如果你是 NAT 模式这里要改成-m,TUN 模式用-i,记不住这三个参数等于白配;-w是权重,weight 越大接收的请求比例越高,适用于后端机器性能不均匀的场景。
配置完用ipvsadm -Ln查看规则,用ipvsadm -Ln --stats看统计。如果看到 TCP 192.168.1.100:80 下面列着三台 RS,每台 RS 后面标注的 Route 类型是 Local,说明配置正确。
这里有个很重要的细节:Director 不需要开启 IP 转发,也不要对 VIP 的包做 iptables 的 DNAT,因为 LVS 是在内核 IPVS 层面处理,根本不走 iptables 的 NAT 链。如果你额外加了 iptables 规则,反而可能把数据包改得乱七八糟。
3.3 keepalived 高可用配置
单台 Director 一旦故障,整个负载均衡就废了。生产环境一定会用 keepalived 做高可用,核心就是 VRRP 漂移 VIP。
keepalived 的作用有两个:一是让两台 Director 组成主备,VIP 默认在 Master 上,Master 挂掉后 VIP 漂移到 Backup;二是定期检查后端 RS 的健康状态,发现 RS 故障自动从 LVS 规则里摘掉。
主节点配置的核心片段:
vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 120 advert_int 1 authentication { auth_type PASS auth_pass 123456 } virtual_ipaddress { 192.168.1.100/24 dev eth0 } } virtual_server 192.168.1.100 80 { delay_loop 6 lb_algo wrr lb_kind DR protocol TCP real_server 192.168.1.11 80 { weight 2 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } real_server 192.168.1.12 80 { weight 1 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } real_server 192.168.1.13 80 { weight 1 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } }备份节点的配置只有两处不一样:state BACKUP,priority 100。virtual_router_id必须和主节点完全一致,否则 VRRP 不认对方;auth_pass也要一致,否则认证失败。主备的lb_algo、lb_kind、real_server 列表必须一致,不然 VIP 切换到备份节点后负载策略就变了。
这里我推荐把健康检查从 TCP_CHECK 升级成 HTTP_GET,因为 TCP_CHECK 只能确认端口通不通,如果后端应用卡死了但端口还开着,TCP_CHECK 是查不出来的。用 HTTP_GET 检查一个具体的健康检查 URL 或者首页,更接近用户真实访问效果:
real_server 192.168.1.11 80 { weight 2 HTTP_GET { url { path /healthz status_code 200 } connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } }keepalived 启动后,确认 VIP 落在 Master 节点上,可以用ip addr show eth0检查。测试高可用时,直接停掉 Master 上的 keepalived 或直接关机,VIP 应该在几秒内漂移到 Backup 上,整个过程中 Web 服务不中断。
3.4 上线前的验证清单
所有配置完成后,不要急着把流量切过来,先按下面的清单过一遍:
- 在 Director 上执行
ipvsadm -Ln,确认三条 RS 记录都在; - 在每台 RS 上执行
ip addr show lo,确认 lo:0 上绑定了 VIP,掩码是 /32; - 在每台 RS 上执行
cat /proc/sys/net/ipv4/conf/eth0/arp_ignore,确认输出是 1; - 从客户端机器
ping VIP,确认能通,并且arp -a看到的 VIP 对应 MAC 是 Director 的 MAC,而不是任何一台 RS 的 MAC; - 连续多次访问
http://VIP,在每台 RS 上tail -f /var/log/nginx/access.log,确认请求被分发到了多台机器; - 把其中一台 RS 的 nginx 停掉,等 keepalived 健康检查周期结束(delay_loop 时间),确认
ipvsadm -Ln里这台 RS 被标记为不可用或直接被移除。
这套验证流程走完,基本可以放心上线。
4. 常见问题与排查经验
4.1 连不通时,按这七个点逐层排查
DR 模式配置完后连不通,是每个人都会遇到的事。我总结了七个排查点,按顺序查,效率最高:
第一,确认 VIP 能不能在 Director 上 ping 通。如果不能,检查 Director 的 VIP 绑定有没有生效,以及网络是否连通。
第二,检查 RS 的 lo:0 上是否绑定了 VIP。命令是ip addr show lo,很多人在 RS 上忘了执行这一步,RS 收到包后直接丢弃,表现为 Director 上能看到连接统计在增长,但 RS 上抓包一点请求都没有。
第三,检查 RS 的 ARP 抑制是否生效。这里要特别注意,很多发行版的 network 服务重启后会重置 sysctl 配置,你得确认/etc/sysctl.conf写对了。一个简单的测试方法:在第三方机器上arp -d 192.168.1.100清缓存,然后ping VIP,再arp -a看 MAC 是不是 Director 的。如果看到 RS 的 MAC,说明 ARP 抑制没起作用。
第四,检查 RS 上的 Web 服务监听地址。用netstat -tlnp看 80 端口是不是监听 0.0.0.0 或 VIP,如果只监听自己的物理 IP,请求到了也进不了应用。
第五,检查 RS 的默认路由。ip route show确认默认网关指向真正的路由器,而不是 Director。这个错误非常隐蔽,配置完一开始可能能通,一旦 Director 重启或者 VIP 漂移,流量就全乱了。
第六,检查防火墙。RS 本机 iptables 如果开了,注意要放行目标 IP 为 VIP 的流量,因为 RS 认为 VIP 是自己的地址,防火墙规则也会对 VIP 流量生效。这个坑我见过太多次了,服务器上明明有-A INPUT -p tcp --dport 80 -j ACCEPT,但那是针对物理 IP 的,目标 IP 是 VIP 的数据包可能被 DROP。
第七,抓包确认数据路径。在 RS 上执行tcpdump -i eth0 host 192.168.1.100,然后客户端访问 VIP。如果 RS 上能抓到请求包但客户端访问失败,问题基本在回包路由或者 RS 的应用层;如果 RS 上什么都抓不到,问题就在 Director 的转发或者二层链路。
4.2 三个经典翻车场景
第一个经典场景:刚配置完是好的,重启后全部失效。原因基本确定是 sysctl.conf 没有持久化,或者 VIP 绑定没有写成开机自启。RS 重启后 lo:0 上的 VIP 消失,arp_ignore恢复默认 0,整个集群瞬间爆炸。解决办法是把绑定命令写到/etc/rc.local,或者用 network-scripts 的 ifcfg-lo:0 文件管理。用 systemd 环境,写一个简单的 service 单元也行。
第二个经典场景:时好时坏,一会能通一会不通。这种大概率是 ARP 缓存错乱。局域网内可能有别的机器也绑了相同的 VIP,或者某台 RS 的 arp_ignore 没设置成功,在响应 VIP 的 ARP。你可以连续ping VIP十几次,观察arp -a的 MAC 是否有变化。一旦发现变来变去,把所有 RS 的 ARP 配置检查一遍。
第三个经典场景:VIP 能通,LVS 规则也在,但就是没有流量进来。这时候看ipvsadm -Ln --stats,如果 InActConn 一直增长但 ActiveConn 归零,说明 TCP 连接没有真正建立起来,往往是 RS 回包路径不对,或者 RS 应用层根本没响应。重点查 RS 的默认网关和应用监听地址。
4.3 压力视角的几点调优
DR 模式虽然很能扛,但该调的地方还是得调。我列几个实际项目中生效明显的优化点:
第一件,合理设置后端权重。不要让所有 RS 都设置 weight 1,根据 CPU 核数、内存大小做加权,让强机器多分担流量,弱机器少承担。这个不是 LVS 本身性能问题,但直接影响整体吞吐。
第二件,注意 Director 的软中断压力。DR 模式虽然响应不回 Director,但入站流量依然不少,高并发时 Director 的 CPU 软中断会飙升。建议在 Director 上启用网卡多队列(RSS),配合 irqbalance 把中断分散到多个 CPU 核心。
第三件,关闭不必要的 conntrack 跟踪。如果 Director 上没有复杂的防火墙需求,尽量不要启用大量 conntrack,因为连接跟踪表一旦满了,新连接会直接丢包。可以在 raw 表里对 VIP 流量设置 NOTRACK,让 LVS 分发的数据包不走 conntrack。
第四件,调整内核 TCP 参数。比如提高net.core.somaxconn和net.ipv4.tcp_max_syn_backlog,防止高并发瞬间握手队列打满。
net.core.somaxconn = 65535 net.ipv4.tcp_max_syn_backlog = 65535 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_fin_timeout = 30这些参数不是 LVS 特有,但对整个负载均衡链路都有帮助。
5. 调度算法与会话保持的补充说明
5.1 生产环境里调度算法到底怎么选
LVS 支持的调度算法很多,但生产环境值得重点用的就那么几个。
轮询(rr)和加权轮询(wrr):适合后端机器规格一样或性能差异明确的场景。特点是简单、均匀、无状态。
最少连接(lc)和加权最少连接(wlc):会把新请求分给当前活跃连接最少的 RS,适合请求处理时间波动比较大的业务。比如一个请求耗时 1 毫秒,另一个请求耗时 3 秒,轮询模式下很难保证均衡,最少连接能拉平负载。wlc 是默认推荐的算法,也是很多生产集群的首选。
源地址哈希(sh):按客户端 IP 哈希到固定 RS,天然实现会话保持。适合那些不想额外开持久连接的场景,但缺点是如果某台 RS 下线,哈希到它上面的客户端全部受影响。
我的建议很直接:后端机器配置一样就选 wrr,处理时间波动大就选 wlc,需要会话保持就用 sh 或开持久连接。
5.2 持久连接和 SH 算法的区别
这两个东西经常有人混在一起。
持久连接(-p 300)不是一种算法,它是在 LVS 虚拟服务上开启的一个功能:同一来源 IP 的请求,在指定时间内都被调度到同一台 RS。即使你的算法是 rr,只要开了 -p,RR 就只在第一次连接时发生,之后相同来源都固定到同一台。适合登录态保存在本机内存的应用。
SH 算法呢,是直接从哈希上保证同一个源 IP 一定会命中同一台 RS,不需要额外维护超时状态。它的前提是源 IP 集合足够分散,否则可能造成倾斜。
在实际使用中,如果应用无状态,直接搞轮询+健康检查就完了;如果应用不能在多节点共享 Session,优先考虑 SH 算法或持久连接。但话说回来,现在的主流架构基本都做无状态设计了,Session 外移到 Redis 之类的缓存层,这比依赖 LVS 做会话保持要可靠得多。
6. 结尾
我在实际项目里被 LVS DR 模式折磨过很多次,最后发现绝大多数事故都不是 LVS 本身的问题,而是出在 ARP 和路由这两件小事上。每当你觉得“集群很诡异”的时候,放下那些复杂的调度算法和性能参数,先去检查 RS 的arp_ignore和默认网关。把这两件事吃透,DR 模式其实就像一层窗户纸。另外一个小建议:每台 RS 的配置都尽量做成脚本管理,不要手工敲,毕竟人总有手滑的时候,脚本还能在初始化新节点时自动把所有的坑都填平。