最早接触 LVS 的时候,我其实是带着一堆问号上手的。当时手上有两台 8 核 16G 的机器跑用户服务,单机 CPU 冲到 70% 就不敢再往上加了,加机器不难,难的是怎么把流量“像切豆腐一样”均匀分到多台机器上。Nginx 当然能做七层负载均衡,但一次 HTTP 请求要经过完整的 accept、解析、转发和连接回收,转发链路越长,节点本身变成瓶颈的概率就越大。后来我把眼光放到 Linux 内核自带的一个机制上——LVS(Linux Virtual Server),它是工作在四层的负载均衡方案,内核模块直接在数据包路径上做调度,不建立用户态连接。这篇文章我会从 LVS 的三种工作模式讲起,再给出一套我自己验证过的 LVS-DR + keepalived 部署流程,最后把我踩过的高频报错按排查思路整理出来,正在做架构选型或者要上手部署 LVS 的同学应该能省不少时间。
1. LVS 到底做了什么:内核级四层调度器
1.1 四层和七层负载均衡的分工差异
很多刚接触负载均衡的人会把 LVS 和 Nginx、HAProxy 放在同一个概念里对比,但这两类东西的工作层次完全不同。Nginx 是七层负载均衡,它要完整接收客户端的 HTTP 请求报文,解析完 URL、Header、Cookie 之后再决定转发给哪台后端,客户端和 Nginx 之间有完整的 TCP 连接生命周期,Nginx 和后端之间又要重新建立一条 TCP 连接。整个过程很灵活,能做的路由规则非常多,但也因此承担了更多 CPU 和内存开销。
LVS 则直接在内核态的 netfilter 钩子上做转发,它不关心应用层内容,只识别四层头里的 IP 和端口。数据包到达 Director(调度器)后,LVS 根据调度算法选一台后端,然后在内核里改写报文头部并转发出去。这个动作发生在内核协议栈内部,不会把数据包拷贝到用户态,所以单台 Director 能支撑的吞吐量通常比七层代理高一个量级。用个不严谨但好理解的类比:Nginx 像是快递中转站,每个包裹都要开箱验货再重新封箱;LVS 像是高铁轨道切换,它不拆包裹,只把车头扳到对应轨道上。
1.2 LVS 的组成:ip_vs 内核模块 + ipvsadm 控制工具
LVS 由两部分组成:内核里的 ip_vs 模块,以及用户态的 ipvsadm 管理工具。主流的 Linux 发行版内核都默认编入了 ip_vs 模块或者把它做成可加载模块,所以我们不需要重新编译内核,大部分情况下只需要装一个 ipvsadm 就能开始操作。
装完之后先确认模块加载正常:
bash复制modprobe ip_vs
lsmod | grep ip_vs
能看到 ip_vs 相关输出就行。如果希望开机自动加载,可以在 /etc/modules-load.d/ 下放一个配置文件,避免重启后规则生效但 IPVS 调度能力没起来。
ipvsadm 负责增删虚拟服务、增删后端 Real Server、修改调度算法、查看连接表和统计信息。它本身是一条命令行工具,所有规则都直接写进内核的 IPVS 表里,不像 Nginx 那样需要 reload 配置才生效,这对自动化运维很友好。
1.3 典型适用场景
从我实际接触的架构看,LVS 最常见的用武之地是这几类:
- 多台 Web 服务或 API 服务的前置统一入口,要求后端拿到真实客户端 IP;
- 四层 TCP/UDP 服务负载均衡,比如 MySQL 从库、Redis、MQ、DNS 等;
- 作为 Nginx 集群前面的“总闸”,先由 LVS 把流量分给多台 Nginx,再由 Nginx 做七层路由,形成两级负载均衡架构。
需要特别说明的是,LVS 对后端的健康检查能力很弱,原生的 ipvsadm 只负责转发,不管后端挂没挂。所以生产环境基本都会搭配 keepalived 或者自己写健康检查脚本,让调度器能够自动摘除故障节点,这个后面部署部分会专门讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 三种工作模式的流量路径拆解与选型
LVS 有 NAT、DR、TUN 三种工作模式,理解它们最好的方式不是背概念,而是跟着数据包走一遍。
2.1 NAT 模式:Director 做双向转发
NAT 模式下,客户端访问的是 VIP(虚拟 IP),Director 收到数据包后把目标地址从 VIP 改写成后端 RIP,转发给 Real Server。Real Server 处理完请求后,把响应包发回给 Director(因为 Real Server 的默认网关指向 Director),Director 再把源地址改回 VIP 发回给客户端。
这个模式的优点是后端服务器只需要把网关指向 Director 的内网 IP,不需要在服务器上绑定 VIP,也不用处理 ARP 抑制,对后端的侵入非常小。缺点是客户端请求和服务器响应都要经过 Director,一旦响应体远大于请求体,Director 的网络吞吐会成为明显瓶颈。所以 NAT 模式更适合响应体较小、后端机器数量不多的内网场景。
2.2 DR 模式:Director 只改 MAC,返回流量直连客户端
DR 模式是目前生产环境最主流的用法。数据包到达 Director 后,LVS 不会修改源 IP 和目标 IP,只把目标 MAC 地址改成选定 Real Server 的 MAC 地址,然后在同一个二层网络里把包发出去。Real Server 收到包后发现自己确实是目标 IP(因为服务器上绑定了 VIP),就正常处理并直接把响应包通过自己的网卡返回给客户端。
关键点在于:Real Server 响应时不再经过 Director,返回流量直接走它自己的默认网关出去。所以 Director 只承担入站流量的调度,出站流量完全旁路,这让 Director 的压力比 NAT 模式小很多,也是 DR 模式能支撑大并发的原因。
代价是 Real Server 上必须配置一个 VIP 地址,并且要做 ARP 抑制,否则整个局域网里会有多台机器抢答 VIP 的 ARP 请求,流量直接乱掉。这些坑我会在部署章节详细展开。
2.3 TUN 模式:跨网段封装转发
TUN 模式把客户端数据包再用一层 IP 包封装起来,通过隧道转发给不在同一网段的 Real Server,Real Server 解封装后处理请求,再直接返回给客户端。它解决了 DR 模式要求后端必须在同一二层网络的问题,适合跨机房、跨地域的调度场景,但隧道封装本身有额外带宽开销,配置也更复杂。除非确实有跨网段需求,否则大多数场景用不上。
2.4 模式对比与选型建议
| 对比项 | NAT | DR | TUN |
|---|---|---|---|
| 返回流量是否经过 Director | 是 | 否 | 否 |
| Real Server 是否需要绑定 VIP | 否 | 是 | 是 |
| Real Server 是否要求同网段 | 通常是 | 必须同二层网络 | 可跨网段 |
| 是否需要 ARP 抑制 | 否 | 是 | 否 |
| 后端网关配置 | 指向 Director | 指向自身默认网关 | 指向自身默认网关 |
| Director 瓶颈风险 | 高 | 低 | 中 |
我自己的选型逻辑很简单:同一机房内优先 DR,因为性能好、部署也不复杂;后端机器不方便绑定 VIP 或者需要后端隐藏真实 IP 时用 NAT;只有异地多活、跨机房调度才考虑 TUN,但现在很多团队直接用更上层的全局负载均衡方案替代了。
3. 调度算法怎么选:从 rr 到 wlc 的取舍
3.1 静态调度算法
静态算法不关心后端当前负载和连接数,只按固定策略分发。
rr(轮询)是最简单的算法,请求按顺序轮流发给每台后端。后端配置差异小、请求处理时长接近时,rr 的表现很稳定。wrr(加权轮询)在 rr 基础上给每台后端配置权重,权重高的机器分到更多请求,适合集群里机器规格不一致的情况。
dh(目标地址哈希)按目标 IP 做哈希取模,sh(源地址哈希)按客户端 IP 做哈希。这类算法天然带会话保持特性:同一个客户端 IP 总是被分到同一台后端。但缺点是后端节点数量变化会导致哈希结果大范围漂移,扩容缩容的代价比较大。
3.2 动态调度算法
动态算法会参考后端的实时连接数,核心思路是“谁空闲就把请求给谁”。
lc(最少连接)永远选择当前活跃连接数最少的后端。wlc(加权最少连接)在 lc 基础上加上权重计算,实际分配时会综合考虑权重和连接数,这也是 LVS 默认推荐的算法。
简单理解 wlc 的取值逻辑:后端权重做分母,连接数做分子,最终选比值最小的节点。比如 RS1 权重 2、当前连接 100,RS2 权重 1、当前连接 40,那 RS1 的比值是 50,RS2 的比值是 40,请求会发给 RS2。这个公式不是让你手算,而是告诉你在长连接场景下 wlc 确实比 rr 更合理。
lblc、lblcr 是带“局部性”的动态调度算法,会尽量把来自同一目的地址的请求分到之前选中的目标服务器,适合 cache 类服务。sed、nq 属于较少用的变体,日常场景用 wlc 就够了。
3.3 持久连接与会话保持
生产环境经常会遇到一个问题:客户端登录后状态存在后端内存里,下一个请求被 LVS 分到另一台机器就丢了会话。解决办法是在虚拟服务上开启持久连接,让同一来源 IP 的请求在一段时间内始终发给同一台后端。
bash复制ipvsadm -A -t 192.168.1.100:80 -s wlc -p 600
-p 600 表示持久连接超时 600 秒,超过这个时间没有新请求就重新调度。注意持久化和调度算法是两回事,持久化管的是“同一个客户端去哪个桶”,调度算法管的是“桶里最初怎么选后端”,两者可以叠加。
4. LVS-DR + keepalived 部署实操
这一节是整篇文章的核心,我直接给一套可以照抄的部署流程。下面用的是 CentOS 7.9 的命令,Ubuntu 系除了包管理器不一样,其余基本通用。
4.1 节点规划与网络拓扑
| 节点 | IP | 角色 |
|---|---|---|
| Director 主 | 192.168.1.10/24 | 调度器主节点 |
| Director 备 | 192.168.1.20/24 | 调度器备节点 |
| Real Server 1 | 192.168.1.11/24 | 后端 Web 服务 |
| Real Server 2 | 192.168.1.12/24 | 后端 Web 服务 |
| VIP | 192.168.1.100 | 对外提供服务的虚拟 IP |
所有机器在同一个二层网络里,VIP 由两台 Director 通过 VRRP 协议选举产生,平时在主节点上,故障时漂移到备节点。
4.2 Director 端:安装 ipvsadm 与内核参数
两台 Director 都执行:
bash复制yum install -y ipvsadm keepalived
确认内核模块:
bash复制modprobe ip_vs
lsmod | grep ip_vs
开启 IP 转发。DR 模式真正起作用的是内核路由和转发逻辑,保险起见统一开启:
bash复制sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p
4.3 Director 端:配置虚拟服务与转发规则
先用命令行方式验证 IPVS 规则能正常建立:
bash复制ipvsadm -A -t 192.168.1.100:80 -s wlc
ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.11:80 -g -w 1
ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.12:80 -g -w 2
ipvsadm -Ln
这里 -g 表示 DR 模式,-w 是权重。用命令行添加规则只是为了确认命令没有问题,最终生产环境我不会这样手动加,而是交给 keepalived 自动管理,因为 keepalived 配置里定义了 real_server 之后会自动生成 IPVS 规则,手动加反而可能出现规则重复。
如果你暂时不用 keepalived,只想让 ipvsadm 规则开机自动生效,可以这样保存:
bash复制ipvsadm-save > /etc/sysconfig/ipvsadm
systemctl enable ipvsadm
4.4 Real Server 端:VIP 绑定与 ARP 抑制
这一步是整个 DR 模式最容易出错的地方。Real Server 需要把 VIP 绑定到回环接口上,同时必须抑制 ARP 响应,否则客户端在局域网里发起对 VIP 的 ARP 请求时,Real Server 也可能应答,流量就不再经过 Director 了。
先临时配置回环接口:
bash复制ifconfig lo:0 192.168.1.100 netmask 255.255.255.255 up
注意掩码必须是 255.255.255.255,也就是 /32,表示这个地址只在本地生效,不走 ARP 广播。如果配成 24 位掩码,会引发路由和 ARP 冲突。
再配置 ARP 抑制参数:
bash复制echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce
为了开机持久化,写入 sysctl 配置:
text复制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
arp_ignore=1 的意思是:只应答目标 IP 正好落在本机网卡地址上的 ARP 请求。arp_announce=2 的意思是:发送 ARP 报文时,尽量使用能到达目标地址的本地地址。这两条配合起来,Real Server 就不会对外宣告自己拥有 VIP,但又能正常接收发给 VIP 的数据包。
4.5 转发验证与连接表观察
规则都配好后,在客户端访问 VIP:
bash复制curl http://192.168.1.100/
然后在 Director 上查看连接和统计:
bash复制ipvsadm -Ln --stats
ipvsadm -Lnc
--stats 能看到每台后端累计转发的包数和字节数,-Lnc 能看到当前连接表,里面会列出源地址、目标地址、后端地址和连接状态。如果两个后端都有流量增长,说明转发链路已经通了。
还可以在 Real Server 上用 tcpdump 抓包确认:
bash复制tcpdump -i any host 192.168.1.100 -nn
能抓到来自客户端 IP 的 HTTP 请求包就说明调度和转发都正常。DR 模式下后端看到的源 IP 就是真实客户端 IP,这个特点在做日志分析时非常好用。
4.6 keepalived 实现 Director 高可用
单台 Director 只是解决了流量分发问题,一旦 Director 本身宕机,整个集群就瘫痪了,所以生产环境必须做高可用。我习惯用 keepalived 同时承担两个职责:VRRP 虚拟 IP 漂移和 IPVS 健康检查。
主节点 keepalived 配置如下:
text复制global_defs {
router_id LVS_MASTER
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass YourPass
}
virtual_ipaddress {
192.168.1.100
}
}
virtual_server 192.168.1.100 80 {
delay_loop 6
lb_algo wlc
lb_kind DR
protocol TCP
persistence_timeout 60
real_server 192.168.1.11 80 {
weight 1
HTTP_GET {
url {
path /health
status_code 200
}
connect_timeout 3
nb_get_retry 3
delay_before_retry 2
}
}
real_server 192.168.1.12 80 {
weight 2
HTTP_GET {
url {
path /health
status_code 200
}
connect_timeout 3
nb_get_retry 3
delay_before_retry 2
}
}
}
备节点配置基本一样,只需要改三处:state 改成 BACKUP,priority 改成 90,router_id 可以改成 LVS_BACKUP。virtual_router_id 两台必须一致,auth_pass 必须一致。
这里有个很容易忽视的点:keepalived 配置里的 real_server 段会自动生成 IPVS 规则。所以不要在启动 keepalived 之前手动跑一堆 ipvsadm -a 加后端,否则 keepalived 起来后可能重复添加或出现规则错乱。我实际部署时只手动 ipvsadm -A 建虚拟服务做连通性验证,最终集群环境完全由 keepalived 管理。
启动并设置开机自启:
bash复制systemctl start keepalived
systemctl enable keepalived
systemctl status keepalived
在主节点上看 IPVS 表,会发现 keepalived 已经自动把规则建好了:
bash复制ipvsadm -Ln
5. 部署后一踩一个准的高频报错与排查链路
5.1 ipvsadm 报 different numbers of ports:fwmark 和端口表不匹配
这个报错我在用 fwmark 做多端口聚合时遇到过。命令大致是这样:
bash复制iptables -t mangle -A PREROUTING -p tcp --dport 80 -j MARK --set-mark 1
iptables -t mangle -A PREROUTING -p tcp --dport 443 -j MARK --set-mark 1
ipvsadm -A -f 1 -s rr
ipvsadm -a -f 1 -r 192.168.1.11:80 -g
最后一条命令会直接报 ipvsadm: different numbers of ports。原因在于 -f 1 定义的是基于 fwmark 的虚拟服务,这种服务的端口数是 0;而后端条目 -r 192.168.1.11:80 带了端口,端口数是 1,ipvsadm 检测到前后端口数量不一致就拒绝执行。
修法是把后端的端口去掉,让它和虚拟服务的端口数量保持一致:
bash复制ipvsadm -a -f 1 -r 192.168.1.11 -g
DR 模式本身不会对端口做改写,数据包到达后端时目的端口还是原始端口 80,后端正常监听 80 就能收到。所以我一开始给后端加端口属于多此一举,反而触发了 ipvsadm 的校验逻辑。
如果你确实需要转发到后端的非 80 端口,比如后端监听 8080,那就不要用 fwmark 聚合,直接拆成两个带端口的虚拟服务,一条规则管 80,一条规则管 8080。
5.2 VIP 能访问但后端收不到流量
现象是客户端 curl VIP 有响应,但 Real Server 的应用日志里看不到任何请求。这种问题九成出在 ARP 抑制没做好,或者 Real Server 的 VIP 没绑对。
排查链路我一般这么走:
- 先在 Director 上执行
ipvsadm -Lnc,看连接表里有没有 SYN 记录。如果连接表有记录但后端日志空白,问题基本在 Real Server; - 在 Real Server 上执行
ip addr show lo,确认 VIP 确实挂在 lo 上且掩码是 32; - 在 Real Server 上执行
cat /proc/sys/net/ipv4/conf/lo/arp_ignore,确认是 1; - 在客户端机器上
ping -I eth0 192.168.1.100,看 ping 的响应 MAC 是什么。正常情况下应该是对应 Director 主节点的 MAC,如果出现 Real Server 的 MAC,说明 ARP 抑制没生效。
还有一个很少被注意的点:如果 Real Server 上有多个网卡,conf/all/arp_ignore 和 conf/eth0/arp_ignore 都建议设置,不要只设 lo。
5.3 高可用切换后连接中断怎么办
Director 主节点宕机,keepalived 会把 VIP 漂移到备节点,但已经建立的 TCP 连接会断。原因很简单:连接表存在主节点内核里,备节点的 IPVS 表里没有这些连接记录,客户端发来的后续数据包找不到对应转发条目,连接自然断掉。
这个问题在 HTTP 短连接场景下基本无感,客户端重新发起一次请求就好。如果是长连接或者 WebSocket,单靠 keepalived 解决不了,要么在应用层做连接重连,要么引入内核连接同步机制。对大多数业务来说,让应用支持断线重连比在 LVS 层强行同步连接表更实际。
5.4 会话保持不生效的排查
开启持久化之后还是会出现会话漂移,先别怀疑 LVS,先看 keepalived 配置里 persistence_timeout 是不是真的生效了。用命令确认:
bash复制ipvsadm -Ln
虚拟服务那一行最后面应该能看到类似 persistent 600 的字样。如果没有,说明配置没生效,检查 keepalived 配置里是否把 persistence_timeout 写在了 virtual_server 块内而不是外面。
另外要记住,持久化按源 IP 做哈希,客户端如果走代理,所有请求可能都来自同一个代理 IP,这种场景下会全部落到同一台后端,看起来像“负载不均”,实际上是被代理出口 IP 汇聚了。
6. 把 LVS 的日常维护和监控补完整
6.1 用 ipvsadm 看实时状态
部署完成只是第一步,后续运维才是大头。我自己最常用的几条查询命令:
bash复制ipvsadm -Ln --stats
ipvsadm -Ln --rate
ipvsadm -Lnc
第一条看累计转发包数、字节数,第二条看每秒速率,第三条看当前连接表。后端权重调整:
bash复制ipvsadm -e -t 192.168.1.100:80 -r 192.168.1.12:80 -w 3
临时摘除某台后端:
bash复制ipvsadm -d -t 192.168.1.100:80 -r 192.168.1.11:80
但注意,如果规则由 keepalived 管理,直接用 ipvsadm 增删规则会被 keepalived 下次健康检查周期拉回来,所以生产环境改后端应该改 keepalived 配置而不是直接动 IPVS 表。
6.2 keepalived 状态确认
判断当前哪台 Director 在活跃状态,最直接的方法是看 VIP 落在谁身上:
bash复制ip addr show eth0 | grep 192.168.1.100
看到 VIP 的节点就是当前活跃调度器。也可以看 keepalived 日志,主节点会打印 Entering MASTER STATE,备节点打印 Entering BACKUP STATE。
6.3 接 Prometheus 和告警
LVS 的监控数据其实很好采集,node_exporter 默认就带 IPVS 采集器,暴露的指标包括 node_ipvs_connections_total、node_ipvs_incoming_packets_total、node_ipvs_outgoing_packets_total 等。在 Prometheus 里直接按 instance 和 job 维度做查询即可。
我自己比较关注两个指标:一是入站包和出站包的差值,DR 模式下出站包远大于入站包是正常的,因为响应流量不走 Director;二是 node_ipvs_connections_total 是否接近连接表上限,如果持续高位就要考虑扩容或优化连接超时。
连接超时可以用 ipvsadm 调整,默认 TCP 空闲超时比较长,可以通过下面的命令调短:
bash复制ipvsadm --set 120 120 300
这组数字分别对应 TCP 空闲时间、TCP 持久连接时间和 UDP 空闲时间。调短超时能更快回收不活跃连接,降低连接表压力。
日常巡检时我个人习惯把指标和告警拆成两层:第一层监控 VIP 和 keepalived 主备状态,第二层才看 IPVS 的转发量和后端健康检查结果。先保证入口活着,再排查调度细节,排查链路会清晰很多。
