LVS 这个技术,我在生产环境里摸爬滚打了快十年,每次和同行聊起负载均衡,总绕不开它。你说它老,确实老——章文嵩博士早在 1998 年就提出了这个方案,二十多年过去了,Nginx、Envoy 这些新生代轮番登场,但 LVS 依然稳稳地蹲在大量互联网公司的入口层,扛着每天数以亿计的请求。原因很简单:它在 Linux 内核里工作,工作在 TCP/IP 协议栈的四层,转发性能几乎可以打到硬件上限,这是任何用户态软件都难以企及的优势。
这篇文章我不打算给你念说明书,而是把 LVS 真正讲透。从三种工作模式的底层原理,到调度算法的选型逻辑,再到基于 Keepalived 的高可用集群怎么一步步搭起来,最后专门聊聊我知道很多人踩过坑的“空闲超时”问题——这个词最近在运维圈讨论得特别多,因为长连接场景下它直接决定你的业务会不会莫名断连、负载均衡器会不会出现连接泄漏。
这篇文章适合谁看?如果你正在选型负载均衡方案,或者已经上了 LVS 但遇到连接异常、转发不均、VIP 漂移不生效这些问题,建议你花二十分钟认真读完。我会把原理、配置、排障全串起来讲,保证你能直接用到生产环境里。
1. LVS 到底是干什么的:从一次真实的容量危机说起
1.1 我在什么情况下决定用 LVS
大概几年前,我负责的一个业务系统遇到了一个特别典型的瓶颈:前端用两台 Nginx 做七层反向代理,后面挂了十几台应用服务器。平时流量平稳还好,一到促销活动,Nginx 的 CPU 使用率就直线飙升,单机连接数很快顶到上限,用户开始出现请求超时。当时我们做了一个压力测试,发现 Nginx 在这种高并发短连接场景下,单机极限也就支撑几万 QPS,而且 CPU 大量消耗在 HTTP 协议解析和 epoll 事件处理上。
那时候我就意识到,业务入口需要再做一层“更薄”的负载均衡,把 TCP 层的流量分发交给 LVS 来处理。LVS 不需要解析 HTTP 协议,它只关心数据包的四元组——源 IP、源端口、目的 IP、目的端口,然后按照预设的调度算法,把数据包送给后端某台真实服务器。它运行在内核空间,直接操作 sk_buff,处理一个包的开销远小于用户态程序,这就是它能撑起千万级并发连接的底层原因。
你可能要问:Nginx 和 LVS 不是重复了吗?其实它们是分工关系。LVS 负责四层分发,把海量连接均匀地打给后端的 Nginx 集群;Nginx 再对每个连接做七层路由,根据 URL、Header 等应用层信息把请求转发到对应的业务服务。这样分层之后,LVS 扛住了连接压力,Nginx 的负担也大大减轻。这个架构后来被叫做“四层入口 + 七层接入”的标准姿势,至今仍在大量公司沿用。
1.2 LVS 与 Nginx、HAProxy 的定位差异
很多新手容易把 LVS 和 Nginx、HAProxy 混为一谈,觉得都是做负载均衡的。但在真正的架构设计里,这三者的定位差异非常大。从工作层面看,LVS 活在四层,只认 TCP/UDP,不关心包里面的应用数据;Nginx 和 HAProxy 虽然也能做四层,但它们的强项在七层,可以做 URL 路由、Header 改写、SSL 卸载等高级功能。
从性能层面看,LVS 的处理能力通常比 Nginx 高一个数量级。我自己实测下来,同样是普通 x86 服务器,LVS 单机转发性能可以轻松跑到百万级并发连接,而 Nginx 在同样条件下能维持的并发和 QPS 要低得多。这背后的原因在于 LVS 挂载到了 Netfilter 框架的钩子上,在数据包进入协议栈的关键路径上完成转发,几乎不产生用户态和内核态的上下文切换。
从高可用角度看,LVS 本身不提供健康检查,必须配合 Keepalived 使用。Keepalived 通过 VRRP 协议实现 VIP 漂移,同时对后端 RS 做健康探测,一旦发现某台 RS 挂了,就自动从 LVS 的转发列表里摘掉。而 Nginx 和 HAProxy 自带健康检查和故障转移机制,部署起来更省心,但性能和并发上限也受限于用户态处理模型。
所以我的选型建议是:入口层用 LVS + Keepalived 做四层高可用和流量分发,后面接 Nginx 或业务网关做七层路由。如果你的系统规模不大、并发几千这种,直接上 Nginx 就够了,没必要上 LVS,因为运维成本也是需要考虑的因素。
2. LVS 三种工作模式的核心原理拆解
2.1 VS/NAT:最直观也最容易理解的模式
VS/NAT 模式最像我们想象中的负载均衡:客户端把请求发给 VIP,LVS 作为网关,把数据包的目的 IP 从 VIP 改写成选中的后端 RS 的 IP,然后转发出去;后端 RS 处理完请求后,把响应包再发回给 LVS,LVS 再把源 IP 改回 VIP,送回客户端。整个过程中,客户端感知不到后端真实服务器的存在,后端 RS 的默认网关必须指向 LVS。
这个模式的优点是实现简单、拓扑清晰,后端 RS 只需要跑普通的 Linux 系统,不需要额外配置。但缺点也很致命:所有流量都要经过 LVS,包括响应流量,这就让 LVS 成了瓶颈。比如后端 RS 网卡是千兆,LVS 入口是千兆,那整个系统的吞吐上限就是 LVS 网卡的速度。我在早期给一个小型电商系统部署时用过 NAT 模式,后来流量涨上来之后,LVS 的网卡直接被打满,被迫切换到了 DR 模式。
NAT 模式还有一个容易忽略的细节:如果后端 RS 和应用需要记录客户端真实 IP,那么必须开启 LVS 的 persistency 或者在 RS 上配置合适的日志格式来保留连接跟踪信息。另外,LVS 在 NAT 模式下会改写数据包的源和目的地址,所以 LVS 本机必须开启 IP 转发功能,这个在/etc/sysctl.conf里设置net.ipv4.ip_forward = 1即可。
2.2 VS/DR:生产环境用得最多的模式
VS/DR(Direct Routing)模式是我在生产环境里用得最多的方案。它的核心思路是:LVS 只负责把客户端的请求包转发给选中的 RS,但 RS 处理完之后直接把响应包回给客户端,不再经过 LVS。这样一来,LVS 只承载进向流量的一半,出口带宽完全由各台 RS 自己承担,整个系统就没有单一瓶颈了。
这里面的关键在于 LVS 和数据包是同一网段的二层网络内通信,LVS 收到请求包后,不修改任何 IP 地址,只是把数据链路层的目的 MAC 地址改写为选中 RS 的 MAC 地址,然后从网卡扔出去。由于目标 IP 没有变,所以 RS 必须让本机悄悄“认领”这个 VIP——通常做法是把 VIP 绑定在 RS 的 lo 接口上,并抑制 ARP 响应,避免 RS 对客户端的 ARP 请求产生应答。
我在第一次部署 DR 模式时犯过一个经典的错误:RS 上绑定了 VIP,但没有做 ARP 抑制。结果整个网段的交换机 ARP 表完全错乱,客户端访问 VIP 的包被随机分发到某一台 RS 上,整个集群直接瘫痪。后来查了内核文档才明白,必须在 RS 上设置net.ipv4.conf.all.arp_ignore = 1和net.ipv4.conf.all.arp_announce = 2,让 RS 只对接收接口上的 IP 做 ARP 应答,并且禁止对外宣告不属于本接口的 VIP 地址。
2.3 VS/TUN:跨机房场景的选项
VS/TUN 模式的原理是在 LVS 和 RS 之间用 IP 隧道封装数据包。LVS 把客户端的请求包封装在一个新的 IP 包里,目标地址为 RS 的 IP,发送出去;RS 解封装后得到原始的请求包,处理完之后直接把响应包回给客户端。这种模式适合 RS 分布在多个机房的场景,物理位置不受二层网络的限制。
但实事求是地讲,我很少在生产里用 TUN 模式,因为配置复杂度高,且对网络环境有要求——需要支持 IPIP 隧道。除非你的业务规模大到需要跨地域调度,否则 DR 模式足够用了。RS 上要加载 ipip 模块、创建隧道接口并绑定 VIP,每一步出错都不好排查,不像 DR 模式配置那么成熟和社区经验丰富。
2.4 三种模式的选型对比
| 模式 | 响应是否经过 LVS | 后端服务器要求 | 跨网段 | 配置复杂度 | 适用场景 |
|---|---|---|---|---|---|
| VS/NAT | 是 | 普通 Linux,网关指向 LVS | 支持 | 低 | 小型集群、后端数量少 |
| VS/DR | 否 | 需绑定 VIP 并抑制 ARP | 不支持(同网段) | 中 | 生产环境首选、高并发场景 |
| VS/TUN | 否 | 需支持 IPIP 隧道 | 支持 | 高 | 跨机房、分布式部署 |
从实际运维的角度,我做了一个默认选择:只要 RS 和后端能在同一个二层网络内,一律用 DR 模式;只有网络规划实在没办法把 RS 聚到同网段时,才考虑 NAT 或 TUN。记住一个健康检查提示:NAT 模式因为响应要回 LVS,LVS 本身承载的压力最大,所以在做容量规划时一定要把 LVS 的带宽和连接跟踪表(nf_conntrack)容量算进去,否则流量稍有波动就可能拖垮整个入口。
3. 调度算法怎么选:不只是轮询和加权
3.1 静态调度算法:rr、wrr、dh、sh
LVS 内置了十种调度算法,可以分为静态和动态两大类。静态算法在调度时不考虑后端 RS 的实时负载,只按预定的规则分配连接。最常见的rr(轮询)就是轮流把请求分给每台 RS,适合后端机器性能差不多的场景;wrr(加权轮询)则允许为每台 RS 设置权重,权重高的机器分到的连接更多,适合机器配置有高有低的情况。
还有两种静态算法容易被忽略:dh(目的地址哈希)和sh(源地址哈希)。dh是按客户端请求的目标 IP 做哈希,把同一个目标 IP 的请求固定分发到同一台 RS,适合后端是按业务区分的场景;sh是按客户端源 IP 做哈希,保证同一个客户端始终落在同一台 RS 上,适合需要维持会话一致的场景。
我一开始做 LVS 配置时,偷懒直接用了rr,结果后端两台机器配置差距较大,一台 CPU 已经跑满,另一台还在闲着。后来调整成wrr,给高性能机器设了 3、低性能机器设了 1,整体吞吐立刻上了一个台阶。所以静态算法虽然简单,但别小看了权重的作用,它是你用最低成本实现“负载均衡”手段之一。
3.2 动态调度算法:lc、wlc、lblcr、sed、nq
动态算法的核心思路是根据后端 RS 的实时连接数来分配请求。lc(最少连接)会把新连接分给当前连接数最少的 RS;wlc(加权最少连接)在lc的基础上引入了权重因素。lblcr(基于本地的最少连接复制调度)在目标 IP 哈希的基础之上,结合了最少连接策略,适合做 Web Cache 集群——可以把同一客户端的请求固定到同一台缓存服务器上,又能在后端负载差异较大时进行动态调整。
还有两个动态算法值得提:sed(最短期望延迟)和nq(永不排队)。sed计算每台 RS 的期望延迟来选择目标,nq则可以看成sed的改进版——在任何后端空闲时,直接分配给空闲机器;只有当所有机器都有连接时才用sed的计算结果。sed和nq适合 RS 处理能力差异较大、且新连接请求比较密集的场景。
动态算法在理论上比静态算法更聪明,但有一个前提:LVS 必须维护每个 RS 的当前连接数,这部分信息存放在内核表中,数量巨大时会抢占一定内存。对于大多数场景,wlc已经足够优秀了,不需要刻意追求更复杂的算法。
3.3 实际场景中我推荐的组合
根据这些年踩坑的经验,我整理了一套组合建议。单纯做 TCP 短连接分发(HTTP API 等),直接上wrr或者wlc就行,配置简单、效果稳定;如果你在这个四层入口后面挂的是长连接服务(比如 WebSocket、消息推送、数据库代理),一定要考虑会话保持,用sh或lblcr,否则客户端在握手过程中被分发到不同 RS,连接就会破裂。
另一个重要的点是:调度算法的选择需要和 Keepalived 的健康检查策略配合。假设你后端 RS 有一台出现了半死状态——进程还在、但已经无法正常处理新连接——Keepalived 的 TCP 检查可能发现不了,因为它只探测端口通不通。这种情况下,你把调度算法换成动态的lc或wlc,能稍微缓解一下问题,因为半死机器在处理新请求时会卡住,连接数持续累积,动态算法会逐渐减少发给它的流量。
做容量规划时,也别忘了把调度算法的开销考虑进去。静态算法的 CPU 开销极低,动态算法需要维护连接计数,在大流量场景会多消耗一点 CPU,但相比整体转发开销来说微不足道。真正的影响在内存侧——LVS 自己维护的连接跟踪表(ip_vs_conn)是固定的哈希结构,连接数超过表容量后新连接会被丢掉,后面在聊空闲超时的时候我们还会详细展开这一点。
4. 实战部署:基于 Keepalived 的高可用 LVS 集群
4.1 环境规划与 IP 规划
我会以一个典型的三层架构为例:两台 LVS 节点(一台 Master、一台 Backup),中间通过 Keepalived 的 VRRP 协议共享一个虚拟 IP(VIP);后端三台 RS,运行 Nginx 服务,提供 HTTP 80 端口。网络拓扑非常简单,但足以复用到绝大多数生产环境。
我的规划如下:
- LVS-Master:192.168.1.10,操作系统 CentOS 7.9,配置 2C4G
- LVS-Backup:192.168.1.11,操作系统 CentOS 7.9,配置 2C4G
- VIP:192.168.1.100
- RS-1:192.168.1.21,安装 Nginx,配置 4C8G
- RS-2:192.168.1.22,安装 Nginx,配置 4C8G
- RS-3:192.168.1.23,安装 Nginx,配置 4C8G
注意 DR 模式下 VIP 必须和 RS 在同一网段,因为需要二层广播来响应 ARP。这里选择 DR 模式,因为它是生产环境的首选。另外,我建议 VIP 不要绑定在 LVS 节点的物理网卡上,而是绑定在 Dummy 接口或直接由 Keepalived 管理,这样在故障切换时 VIP 的迁移更干净。
4.2 安装与配置 Keepalived+LVS
在两台 LVS 节点上安装 Keepalived 和 ipvsadm,安装完成后先配置内核参数。为了让 LVS 正常转发数据包,需要开启 IP 转发,并关闭 rp_filter 可能导致的问题。
[root@lvs-master ~]# sysctl -w net.ipv4.ip_forward=1 [root@lvs-master ~]# sysctl -w net.ipv4.conf.all.send_redirects=0 [root@lvs-master ~]# sysctl -w net.ipv4.conf.default.send_redirects=0 [root@lvs-master ~]# sysctl -w net.ipv4.conf.ens33.send_redirects=0 [root@lvs-master ~]# sysctl -psend_redirects要关掉是因为在 DR 模式下,LVS 和 RS 在同一网段,如果系统自动发送 ICMP 重定向报文,可能会干扰路由决策。接下来配置 Keepalived,主节点的配置文件/etc/keepalived/keepalived.conf大概是这样的:
global_defs { router_id LVS_MASTER vrrp_skip_check_adv_addr vrrp_strict } vrrp_instance VI_1 { state MASTER interface ens33 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 123456 } virtual_ipaddress { 192.168.1.100/24 } } virtual_server 192.168.1.100 80 { delay_loop 6 lb_algo wrr lb_kind DR protocol TCP persistence_timeout 600 real_server 192.168.1.21 80 { weight 3 TCP_CHECK { connect_timeout 3 connect_port 80 } } real_server 192.168.1.22 80 { weight 3 TCP_CHECK { connect_timeout 3 connect_port 80 } } real_server 192.168.1.23 80 { weight 3 TCP_CHECK { connect_timeout 3 connect_port 80 } } }备份节点的配置仅需要把state改成BACKUP、priority改成 90,其余保持一致。启动 Keepalived 后,用ip addr show检查 VIP 是否绑定在主节点上。lb_algo wrr是我上面推荐的组合之一——权重均匀、配置简单,后端的 Nginx 负责七层逻辑,所以四层不需要太花哨的算法。persistence_timeout 600表示同一个源 IP 在 600 秒内会固定分发到同一台 RS,这个配置要谨慎,它直接影响会话保持和负载均衡程度,后面我会单独展开。
4.3 后端 RS 的 ARP 抑制配置
RS 的配置是 DR 模式最容易出错的地方。我们需要把 VIP 绑定到每台 RS 的 lo 接口上,同时抑制该接口的 ARP 响应和外发宣告。每台 RS 上执行:
[root@rs-1 ~]# echo "net.ipv4.conf.all.arp_ignore = 1" >> /etc/sysctl.conf [root@rs-1 ~]# echo "net.ipv4.conf.all.arp_announce = 2" >> /etc/sysctl.conf [root@rs-1 ~]# echo "net.ipv4.conf.lo.arp_ignore = 1" >> /etc/sysctl.conf [root@rs-1 ~]# echo "net.ipv4.conf.lo.arp_announce = 2" >> /etc/sysctl.conf [root@rs-1 ~]# sysctl -p [root@rs-1 ~]# ifconfig lo:0 192.168.1.100 netmask 255.255.255.255 uparp_ignore设为 1 的含义是:只有 ARP 请求的目标 IP 正好是本机接收接口上的 IP 时,才进行应答。VIP 绑定在 lo 的别名接口上,按这个规则它不会应答来自外部网络接口的 ARP 请求,这样就避免了多台 RS 同时响应 VIP 的 ARP 请求,导致交换机 MAC 表抖动。arp_announce设为 2 的含义是:主机对外发送 ARP 请求时,永远使用属于本机主接口的 IP 作为源地址,而不会把 VIP 宣告出去。
这两条规则缺一不可。我曾经只配了arp_ignore没配arp_announce,结果 VIP 还是被某台 RS 偶尔宣告了出去,造成间歇性丢包。这些配置必须写进/etc/sysctl.conf,同时把 lo:0 的配置写到/etc/rc.d/rc.local或 NetworkManager 的配置里,确保重启后依然生效。由于 DR 模式要求 RS 对回包不经过 LVS,所以 RS 的默认网关需要指向自己的出口路由器,而不是 LVS。
4.4 验证与压力测试
配置完成后,先用 ipvsadm 检查转发规则是否加载成功:
[root@lvs-master ~]# ipvsadm -L -n IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 192.168.1.100:80 wrr persistent 600 -> 192.168.1.21:80 Route 3 0 0 -> 192.168.1.22:80 Route 3 0 0 -> 192.168.1.23:80 Route 3 0 0然后用curl访问 VIP 验证转发是否正常。反复执行多次,可以看到后端 RS 的访问日志都有请求进来。接下来用 ab 或者 wrk 做压测,观察 LVS 节点的 CPU 使用率、活动连接数和响应时间。
压测时有一点特别重要:你要确认请求来自不同的源 IP。因为配置了persistence_timeout 600,如果压测工具只从本机一个源 IP 发出请求,所有流量都会被固定分发到同一台 RS,根本达不到负载均衡的效果。我的做法是用多台压测机,或者给压测机网卡配置多个 IP,再或者使用 wrk 的-H参数配合 LVS 的-p标记来模拟不同源地址。另外,监控时不要只看 CPU,要看ActiveConn在每台 RS 上的分布是否均匀,这才是负载均衡的真正指标。
5. 空闲超时这个坑:连接超时参数详解
5.1 LVS 的连接超时到底由谁控制
“LVS 负载均衡器 空闲超时”这个热词最近被讨论得很多,原因在于长连接应用越来越普遍,而 LVS 对空闲连接的默认处理机制让不少人踩了坑。LVS 维护着一张连接跟踪表,每个经过转发的连接在这个表里都有一条记录,字段包含客户端 IP、VIP、后端 RS IP、端口、超时时间等。默认情况下,LVS 对 TCP 连接的空闲超时时间是 900 秒,即一条 TCP 连接如果 15 分钟没有数据传输,LVS 就会把它从连接跟踪表里清掉。
这是在 LVS 内核模块中定义的默认值,可以通过ipvsadm --set来调整。这个 900 秒的超时和 TCP 协议本身的 keepalive 机制是两码事,跟后端 Nginx 的 keepalive_timeout 也是两码事。这里最容易产生误解:客户端和后端服务之间明明有心跳包,为什么 LVS 还是把连接掐断了?因为 LVS 只看“数据包是否经过自己转发”,如果心跳包频率低于 900 秒的阈值,LVS 就会认为连接空闲。
有一种经典故障表现为:客户端和服务端之间的连接建立后,在静默一段时间后(通常是 15 分钟左右)再次发数据时,客户端会收到 RST 或连接超时。原因是 LVS 已经删除连接跟踪记录,新的数据包到达 LVS 后找不到对应连接,就会被当作新连接处理;如果调度算法选了rr或wrr,这个包可能被分发到另一台 RS,而原来的 RS 还保留着旧连接状态,于是状态错乱,直接报错。
5.2 空闲超时引发的真实故障案例
我曾经处理过一个生产事故:某业务使用 WebSocket 长连接,客户端的保活心跳是每 10 分钟一次。LVS 默认的超时时间是 900 秒,理论上心跳间隔 600 秒小于 900 秒,不会触发断连。但是在高峰期,LVS 的连接表压力较大,整体性能下降后,部分连接的处理出现延迟,心跳包在 LVS 上排队,实际被转发的时间晚了几分钟,加上某些客户端网络抖动,心跳间隔实际超过了 900 秒,于是大量连接被 LVS 强制清理,前端立刻出现“连接被重置”的报障。
排查时发现,问题并不是后端的 WebSocket 服务挂了,也不是 Nginx 配置错误,而是 LVS 的连接超时设置和业务心跳频率刚好卡在边界上。后来我们把 LVS 的空闲超时时间调大到了 3600 秒,同时优化了客户端的保活心跳策略,把心跳间隔缩短到 5 分钟,并且服务端也开启 TCP keepalive 作为兜底,这个故障就再也没有出现过。
这里面还有一个隐蔽的坑:LVS 的persistence_timeout和连接超时是两套独立机制。persistence_timeout是“持续性超时”,它决定的是同一源 IP 的连接会被持续分发到同一台 RS,超时时间一到,调度决策就重新开始。它不影响已建立的 TCP 连接本身。但如果你把persistence_timeout设得特别长(比如 6000 秒),而 LVS 的连接超时还是默认的 900 秒,就会出现一种奇怪的现象:连接虽然被 LVS 清了,但由于持久性还在,同一客户端的新连接仍然会落到同一台 RS,看起来好像没问题,但旧连接的数据包可能已经找不到了,应用层会报错。
5.3 调整超时参数的正确姿势
调整 LVS 超时参数的命令是ipvsadm --set,格式是ipvsadm --set tcp tcpfin udp,单位是秒。比如把 TCP 空闲超时调成 3600 秒,TCP FIN 等待超时调成 120 秒,UDP 超时调成 300 秒,可以这样执行:
ipvsadm --set 3600 120 300但要注意,这个命令只对当前内核中的 ip_vs 模块配置生效,机器重启后就丢了,必须写入持久化脚本。Keepalived 本身没有提供超时配置的指令,所以两种做法:要么在 Keepalived 启动前通过 systemd 单元执行ipvsadm --set,要么把命令写进/etc/rc.local。如果你使用 LVS 的 direct 模式,超时参数对 UDP 场景尤其重要,因为 UDP 没有连接状态,LVS 只能用超时来清理连接表,否则表会无限膨胀。
调参时我建议遵循几个原则:第一,业务心跳间隔要明显小于 LVS TCP 超时时间,至少留 1.5 到 2 倍的余量;第二,TCP FIN 等待超时不建议设得太大,否则大量处于TIME_WAIT状态的连接会占用表项;第三,如果你的业务全是短连接(像普通 HTTP API),可以适当缩小 TCP 超时时间,比如 300 秒,这样连接表释放更快,LVS 的内存占用更低。短连接业务和长连接业务的超时配置差异非常大,不要一条配置走天下。
# 长连接业务建议 ipvsadm --set 3600 120 300 # 短连接业务建议 ipvsadm --set 300 60 1205.4 超时问题的心跳与保活策略
处理 LVS 空闲超时问题不能只靠调大超时时间,还要从业务层面做好保活。我总结了一套组合策略,在多个项目里验证过有效。应用层心跳是最直接的保活手段,客户端每隔一段时间发送心跳包,间隔时间必须明显小于 LVS 的超时时间。TCP keepalive 是内核层面的保活机制,开启后即使应用层没有数据,内核也会定时发送探测包。默认的 TCP keepalive 探测间隔是 2 小时,这个时间比 LVS 默认超时还要长,所以如果你只靠 TCP keepalive 来保活,建议调小系统参数。
[root@lvs-master ~]# sysctl -w net.ipv4.tcp_keepalive_time=600 [root@lvs-master ~]# sysctl -w net.ipv4.tcp_keepalive_intvl=60 [root@lvs-master ~]# sysctl -w net.ipv4.tcp_keepalive_probes=3tcp_keepalive_time设为 600 秒后,即使应用层没有心跳,内核也会在 600 秒没有数据时开始发送探测包。但这里还有一个细节:TCP keepalive 探测包本身也是数据包,它会不会被 LVS 当作有效流量记录?实测看,LVS 只检测连接是否空闲来判断超时,keepalive 探测包一旦经过 LVS,就会刷新连接的活动时间,因此确实能够保活。不过,LVS 在转发 keepalive 探测包时,如果连接表已被清理,探测包也会触发重新建立连接的问题,所以最终还是得让业务心跳频率和 LVS 超时留有足够余量。
关于四层负载均衡器空闲超时这个问题,我最后的建议是:上线前一定要梳理业务的长连接生命周期,明确哪些连接是长时间静默的,哪些连接是高频交互的,分别设置对应的超时时间。别图省事把所有连接一刀切。
6. 常见问题与排查技巧实录
6.1 高频率故障速查表
部署和维护 LVS 这么久,我把最常见的故障和排查思路整理成一个速查表,方便大家对照处理。
| 现象 | 可能原因 | 排查命令/方法 | 解决方法 |
|---|---|---|---|
| 客户端访问 VIP 超时 | VIP 未绑定成功 | ip addr show检查 Master 和 Backup | 检查 Keepalived 配置和 VRRP 状态 |
| 某些 RS 收不到流量 | RS 的 VIP 绑定或 ARP 抑制配置错误 | ip vsadm -L -n查看 Forward 列 | 检查 RS 的 lo:0 和 sysctl 参数 |
| 高并发下新连接被拒绝 | 连接跟踪表满 | dmesg看 nf_conntrack 报错 | 调整超时时间,扩大表容量 |
| 长连接间歇性断线 | LVS 连接超时小于业务空闲时间 | 查看访问日志时间点 | 调大ipvsadm --set的超时值 |
| 调度不均、某台 RS 过载 | 权重设置不合理或调度算法不当 | ipvsadm -L -n --stats查看连接分布 | 调整权重,改用动态算法 |
| 主备切换后流量中断 | VRRP 配置或 VIP 绑定异常 | systemctl status keepalived,journalctl日志 | 检查state、priority、virtual_router_id |
6.2 排查工具与命令清单
排查 LVS 问题时,我依赖的工具不算多,但每个都能派上大用场。ipvsadm是核心工具,看规则、看连接数、清空规则全靠它。常用命令:
ipvsadm -L -n:列出当前的虚拟服务和后端 RS 列表ipvsadm -L -n --stats:查看累计转发流量和连接数统计ipvsadm -L -n --rate:查看实时速率ipvsadm -L -n --timeout:查看当前超时时间配置ipvsadm -D -t 192.168.1.100:80:删除某条虚拟服务规则
tcpdump是第二个必备工具。排查 ARP 问题时,在 RS 上抓包看有没有异常的 ARP 请求;排查转发问题时,在 LVS 上抓包看数据包有没有被正确改写和转发。抓包命令我通常这样用:
[root@lvs-master ~]# tcpdump -i ens33 host 192.168.1.100 and tcp port 80 -nn -c 100conntrack命令也很有用,它可以查看 LVS 连接跟踪表中的实时状态。如果发现连接数异常高,很可能是超时参数设置不合理导致的泄漏,需要及时清理或调参。最后是ip neigh,查看 ARP 缓存表,确认 VIP 对应的 MAC 地址是不是正确指向 LVS 节点,避免 VIP 漂移。
6.3 独家避坑心得
几个坑是我用教训换来的,写出来希望帮大家省点时间。第一个是Keepalived 的vrrp_strict配置要慎重。这个选项启用后会严格执行 VRRP 协议规范,导致 Keepalived 自动添加一些 iptables 规则,阻断非 VRRP 流量。我之前开启后,VIP 死活无法访问,检查了老半天才发现是vrrp_strict把转发流量拦了。如果你的 LVS 节点启用了系统防火墙,建议关闭vrrp_strict,或者在防火墙里显式放行相关流量。
第二个坑是上线前一定要做“ARP 抑制”的回归测试。很多系统重启之后,rs 的sysctl配置没生效,导致整片网络 ARP 表紊乱。我会在每次版本上线或系统重启后,用一条命令快速验证:在 LVS 节点上 ping VIP,然后查看ip neigh看 VIP 对应的 MAC 地址到底是 LVS 自己的 MAC 还是某台 RS 的 MAC。如果是 RS 的 MAC,说明 ARP 抑制失败了,要立刻去 RS 上检查配置。
第三个坑是关于persistence_timeout的使用。这个参数既会影响调度均衡,又会影响故障转移速度。如果你开启持久性 600 秒,某台 RS 挂了,Keepalived 把它从规则里移除,但已经建立的持久连接仍会尝试发往这台 RS,导致这部分流量直接失败,直到持久性超时才会重新调度。所以对高可用要求严格的系统,建议持久性超时设置短一点,甚至设为 0,把会话保持交给上层应用去解决。
第四个坑是我一直强调的:LVS 是四层负载均衡器,不解析应用层协议,所以在做健康检查时,TCP_CHECK只能探测 TCP 端口通不通,无法判断 HTTP 服务是否真正正常。对于关键业务,建议用HTTP_GET或MISC_CHECK配合自定义脚本,探测应用的健康状态。keepalived 官方支持HTTP_GET,它会实际发起一次 HTTP 请求并检查返回状态码,这样能把“端口通但应用假死”的情况及时摘除。
写在最后的几点经验
LVS 这个老伙计,别看它年龄大,在四层负载均衡领域依然是不可撼动的王者。它的性能优势来自内核态的转发路径,它的可靠性来自 Keepalived 成熟的 VRRP 方案,而它的灵活性来自十种调度算法和多种转发模式。理解了这些底层机制,再配合合理的超时配置和健康检查策略,你就可以在一个高并发入口上稳稳地睡个好觉。
我在实际项目中体会最深的一点是:不要试图让 LVS 做太多事。它就是一个高效的分发器,你的重任是把它和后端服务之间的边界划清楚——LVS 管连接、管分发、管高可用,后端的服务管业务、管会话、管数据一致性。一旦这个边界模糊了,各种诡异的问题就会出现。
如果你现在正在规划新的负载均衡架构,或者已经在为某个“间歇性断连”的问题焦头烂额,不妨回到这篇文章再翻一翻 LVS 的配置和超时参数。最后再分享一个小技巧:每次改完 LVS 配置或 Keepalived 配置后,写一个简单的变更记录,包含改前改后的配置、操作时间、操作原因。这个习惯看似不起眼,但在线上出问题时,它能帮你快速回溯到底是哪儿动了导致故障,省下的排查时间远比你记这些记录的时间多得多。