1. 单机扛不住流量了:为什么我最终选择了 LVS
先讲一个真实场景。去年我做了一个电商类的小项目,上线初期一台 4 核 8G 的云服务器跑 Nginx + PHP + MySQL,日活几千人完全没压力。结果到了某次促销活动,流量突然涨到平时的十几倍,CPU 直接 100%,数据库连接数被打满,页面响应时间从 200ms 飙升到 8 秒。我当时的第一反应是加配置,把机器升到 16 核 32G,但成本翻了几倍不说,没过多久流量再涨,又扛不住了。
这其实是很多团队从单体架构走向集群架构时的必经之路。单机时代,加配置是唯一解法;但一旦流量进入千万级、亿级,或者你有高可用需求,单机的天花板就卡在那里了。这时候你需要的不是更强的单机,而是一组机器组成集群,再有一个“流量调度的大脑”把请求分发给集群里的每一台机器,同时还要保证即使某一台挂了,整体服务也不中断。
这个“大脑”就是负载均衡器。当前主流的负载均衡方案有这几类:
- 软件四层负载均衡:LVS(Linux Virtual Server)、DPVS(基于 DPDK 的 LVS 增强版)
- 软件七层负载均衡:Nginx、HAProxy
- 云厂商自带的 SLB/CLB:本质上底层也是四层+七层组合
我最终选择 LVS,最大的原因是它工作在 Linux 内核态,直接在内核里做流量转发,不经过用户态拷贝,单机并发能力可以做得很高。在同样的硬件条件下,Nginx 作为七层负载均衡器,通常能支撑几万到十几万的并发连接,而 LVS 可以轻松做到几十万甚至百万级,而且延迟更低。它不解析 HTTP 内容,不关心 URL、Cookie、Header,只看 IP 和端口,所以吞吐量极其可观。
这篇文章我就把 LVS 这套东西从原理到实战完整梳理一遍,包括它的三层调度架构、三种工作模式、调度算法选型、与 Keepalived 配合做高可用,以及我在生产环境踩过的坑。适合刚接触集群架构的运维、后端开发,以及正在做架构选型的技术负责人。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. LVS 到底是怎么工作的:三层架构与数据流转全拆解
2.1 三个角色的分工
LVS 的整个体系里只有三种角色,非常清晰:
- Load Balancer(LB / Director):整个集群的入口,负责接收外部请求,然后按照预设的调度算法把请求转发给后端服务器。它就是那个“流量调度的大脑”。
- Real Server(RS):真正处理业务请求的服务器,可以是一台 Nginx、一个 Tomcat、一组 MySQL,或者任何你觉得需要被横向扩展的服务。RS 的数量可以从两台到几百台。
- Shared Storage(可选):如果后端业务涉及文件上传、静态资源,RS 之间需要共享一份数据,这时候要挂 NFS、GlusterFS 之类的共享存储。如果业务本身是无状态的,这层可以省略。
这三者的关系可以类比成餐厅:LB 是大堂经理,负责把所有顾客安排到不同的餐桌;RS 是各个服务员和厨师,真正接待顾客;共享存储则是后厨的公共冰箱,所有厨师都能从里面取食材。
2.2 数据包的一次完整旅程
以最简单的 DR 模式为例,我画一条请求的生命周期(画不出图,我用文字描述):
- 客户端发起 HTTP 请求,目标地址是 VIP(虚拟 IP,也就是 LVS 对外的入口 IP),假设是 192.168.1.100:80。
- 请求到达 LB 的网卡,LB 内核里的 IPVS 模块通过 netfilter 框架的钩子函数拦截到这个包。
- IPVS 根据配置好的调度算法(比如加权轮询),从 RS 列表里选出一台目标服务器,假设是 RS1,IP 是 192.168.1.11。
- LB 把请求数据帧的目标 MAC 地址改写成 RS1 的 MAC 地址,然后原样扔到交换机上(注意,DR 模式不改 IP,只改 MAC)。
- 数据帧到达 RS1 的网卡。因为 RS1 的 lo 接口上也绑定了 VIP,所以 RS1 内核认为自己确实应该接收这个目标 IP 为 192.168.1.100 的包。
- RS1 处理完业务,生成响应包,源 IP 是 VIP 192.168.1.100,目标 IP 是客户端的 IP,直接回给客户端。响应完全不经过 LB。
这个流程最精妙的地方在于:请求流量经过 LB,但响应流量不经过 LB。对很多高带宽业务来说,响应数据往往远大于请求数据,这种“请求走 LB、响应走直连”的模式极大降低了 LB 的压力。
2.3 工作在 Linux 内核里的 IPVS
LVS 的核心组件是 IPVS(IP Virtual Server),它是直接编译进 Linux 内核的模块。你可以把它理解成在内核网络栈里加了一个“流量改写器”,监听 netfilter 框架的几个关键钩子点(LOCAL_IN、FORWARD、LOCAL_OUT),当匹配到 VIP 的流量时,按照你配置的规则改写目标地址或 MAC,然后重新注入内核网络栈。
这种内核态处理方式带来的直接好处是:
- 没有用户态和内核态之间的数据拷贝,性能损耗极低
- 不依赖进程调度,不会有进程上下文切换的开销
- 对 TCP/UDP 都支持,不只是 HTTP
检查你的系统里有没有加载 IPVS 模块,可以执行这个命令:
bash复制lsmod | grep ip_vs
如果没输出,可以用 modprobe 手动加载:
bash复制modprobe ip_vs
modprobe ip_vs_wrr
modprobe ip_vs_rr
modprobe ip_vs_sh
建议你把这些模块加入开机自动加载列表,避免重启后 LVS 失效。
3. 三种工作模式怎么选:NAT、TUN、DR 的对比与取舍
3.1 NAT 模式:最安全但最容易成瓶颈
NAT(Network Address Translation)模式的工作原理是:LB 接收到客户端请求后,通过 DNAT 把目标地址从 VIP 改成 RS 的 IP,然后把包转发给 RS;RS 处理完请求后,把响应包回传给 LB,LB 再通过 SNAT 把源地址改成 VIP,最后发给客户端。
整个过程中,请求和响应都要经过 LB,所以 LB 的网络吞吐量就是整个集群的上限。我实测过,用普通千兆网卡的服务器做 LB,NAT 模式压到 800Mbps 左右就接近极限了,而且 CPU 占用相当高,因为每一个包都要做地址转换。它的优点也很明显:
- RS 可以躲在私有网段里,客户端看不到 RS 的真实 IP,安全性更好
- 只需要 LB 有一个公网 IP,RS 甚至可以没有外网 IP
- RS 的操作系统类型没有限制,只要支持 TCP/IP 就行
3.2 TUN 模式:跨机房调度的最佳选择
TUN(IP Tunneling)模式有点特殊。LB 收到请求后,把原来的数据包整体封装在一个新的 IP 包里,新的目标 IP 是 RS 的 IP,然后通过 IP-over-IP 隧道发出去。RS 收到后解封装,发现自己就是目标,处理完业务后直接把响应包回给客户端。
这个模式最大的价值在于:它可以让后端 RS 分布在不同的物理机房、不同的网段,比如 LB 在北京,RS 可以有一批在上海、一批在广州。隧道是三层协议,可以跨路由传输。
但 TUN 模式有两个明显的代价:一是 IP 封装会带来额外的带宽开销,大约 5%~10%;二是 RS 的内核必须支持 IP 隧道协议,并且要正确配置 tunl 接口。我在实际项目中很少用 TUN,因为跨机房调度用 DNS 分流或者云上多活更成熟,本地部署用 DR 模式更高效。
3.3 DR 模式:生产环境的事实标准
DR(Direct Routing,直接路由)模式是我今天重点推荐的。它的核心原理是:LB 只改写数据帧的目标 MAC 地址,不做 IP 地址转换,然后把帧发送到局域网内。因为目标 IP 还是 VIP,所以每一台 RS 的 loopback 接口上都要绑定 VIP 地址,并且在 RS 上必须关闭对 VIP 的 ARP 响应,否则会跟 LB 抢请求。
DR 模式的优点非常突出:
- 响应流量不需要经过 LB,LB 的负载大幅下降
- 吞吐量和并发能力完全取决于 LB 的网卡处理能力和内核协议栈,不背数据转发的包袱
- 配置相对简单,不依赖隧道协议
缺点就只有两个:第二,RS 和 LB 必须在同一个二层网络里;第二,RS 的网卡硬件上必须支持修改 MAC 地址。
3.4 参数对比汇总
| 特性 | NAT | TUN | DR |
|---|---|---|---|
| RS 与 LB 网络要求 | 同一局域网即可 | 可跨网段 | 必须在同一局域网 |
| 请求是否经过 LB | 是 | 是 | 是 |
| 响应是否经过 LB | 是(瓶颈所在) | 否 | 否 |
| IP 地址转换 | DNAT+SNAT | IP 隧道封装 | 仅改 MAC |
| RS 是否需要绑定 VIP | 否 | 必须 | 必须 |
| RS 是否允许任意 OS | 是 | 需支持隧道 | 是(需关闭 VIP 的 ARP) |
| 吞吐量 | 低 | 中等 | 最高 |
4. DR 模式手工部署全过程:从网络规划到上线验证
4.1 网络拓扑规划
我下面用一个最小可用的例子来演示。假设有三台服务器:
| 角色 | 主机名 | IP 地址 | 网关 |
|---|---|---|---|
| LB | lvs01 | 192.168.1.10 | 192.168.1.1 |
| RS1 | web01 | 192.168.1.11 | 192.168.1.1 |
| RS2 | web02 | 192.168.1.12 | 192.168.1.1 |
| VIP | - | 192.168.1.100 | - |
业务是普通的 HTTP 服务,监听 80 端口,RS1 和 RS2 上分别部署 Nginx,都提供同样的静态页面或者 API 接口。在实际项目中,RS1 和 RS2 的业务代码需要保持一致,数据层如果涉及写操作,要提前规划好同步方案。
4.2 在 RS 上绑定 VIP 并抑制 ARP
这一步是整个 DR 模式最容易出错的地方。如果不理解为什么要这么做,后面出了问题你都不知道从哪儿排查。
RS 必须绑定 VIP 的原因:客户端的请求目标是 VIP,LB 转发过来的数据帧里,源 IP 是客户端 IP,目标 IP 是 VIP,目标 MAC 是 RS 的 MAC。RS 收到这个帧后,网络协议栈会检查目标 IP 是不是本机的地址,如果不是,直接丢弃。所以 RS 必须有一个 IP 地址等于 VIP。
RS 必须抑制 VIP 的 ARP 响应:ARP 协议的作用是把 IP 地址解析成 MAC 地址。如果 RS 的网卡直接绑定了 VIP,当交换机广播寻找 VIP 对应的 MAC 时,RS 会响应说自己就是 VIP。这样一来,客户端的请求可能直接跑到 RS 上,完全绕过 LB,负载均衡就失效了。所以 RS 要把 VIP 绑定在 loopback 接口上,并且配置 arp_ignore=1、arp_announce=2,让系统不要对外通告这个 IP。
在 RS1 和 RS2 上分别执行:
bash复制# 检查当前 ARP 参数
sysctl net.ipv4.conf.all.arp_ignore
sysctl net.ipv4.conf.all.arp_announce
# 临时生效
echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce
# 绑定 VIP 到 lo 接口
/sbin/ip addr add 192.168.1.100/32 dev lo
# 关闭 lo 接口的 ARP 广播
/sbin/ip link set lo up
为了让配置在重启后依然生效,建议写入 sysctl.conf 和 rc.local:
bash复制# /etc/sysctl.conf 追加
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.lo.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.lo.arp_announce = 2
bash复制# /etc/rc.local 追加
/sbin/ip addr add 192.168.1.100/32 dev lo
chmod +x /etc/rc.local
4.3 在 LB 上配置 IPVS 规则
LVS 的配置推荐使用 ipvsadm 工具,这也是目前最主流的管理方式。如果系统里没有,先安装:
bash复制# CentOS / RHEL
yum install -y ipvsadm
# Ubuntu / Debian
apt install -y ipvsadm
然后配置虚拟服务:
bash复制# 添加一条虚拟服务规则:VIP:80,调度模式为加权轮询(wrr)
ipvsadm -A -t 192.168.1.100:80 -s wrr
# 添加后端 RS,-g 表示 DR 模式,-w 设置权重
ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.11 -g -w 1
ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.12 -g -w 2
# 保存规则,避免重启丢失
ipvsadm -S > /etc/sysconfig/ipvsadm
上面的 -g 是 DR 模式的缩写,等价于 --gatewaying。与之对应的是 -m(NAT)和 -i(TUN)。
配置完成后,可以用 ipvsadm -L -n 查看规则状态,输出里应该能看到 VIP 和两台 RS 的记录,以及 A 表示 Active、I 表示 Inactive 的连接状态。
4.4 验证负载均衡是否真正生效
验证分三步走:
第一步,在 LB 上用 curl 测试 VIP 是否通:
bash复制curl http://192.168.1.100/
如果配置正确,你会看到 RS1 或 RS2 上 Nginx 的默认页面。多执行几次,观察返回内容是否在轮换。如果两台 RS 的内容做了不同标记(比如在各自 Nginx 的 index.html 里写一句 this is web01),效果更直观。
第二步,在 LB 上查看连接分发记录:
bash复制watch -n 1 'ipvsadm -L -n --stats'
这个命令会实时刷新,你能看到每个 RS 的转发包数、字节数、活动连接数。用压测工具或者多开几个 curl 并发请求,观察统计值是否按权重分配到两台 RS 上。
第三步,模拟一台 RS 故障。关掉 web01 的 Nginx:
bash复制systemctl stop nginx
这时候继续访问 VIP,请求理论上会被全部转发到 web02,用户侧无感知。但这里有一个隐藏问题:LVS 本身不会自动摘除故障节点,它只吞包,不关心 RS 死活。要解决这个问题,必须引入健康检查机制,也就是后面要讲的 Keepalived。
提示:如果 curl 访问 VIP 出现 Connection refused,先检查 RS 的 Nginx 是否正常监听 80,再检查
ipvsadm -L -n里 RS 的状态是否标红、Forward 列是否为 DR。这些是排错的第一现场。
5. 调度算法选型:从简单轮询到面向长连接的会话保持
5.1 固定调度算法
IPVS 支持十几种调度算法,按是否需要考虑后端实时负载,可以分成固定算法和动态算法两大类。
固定算法里最常用的是:
- RR(Round Robin):纯轮询,一个接一个分配,适合 RS 配置完全相同、请求处理时间接近的场景。配置最简单,但也最容易出现倾斜,比如某个连接是长连接,占着 RS 不释放,后面的短请求就分配到了其他机器。
- WRR(Weighted Round Robin):加权轮询,给每个 RS 配一个权重,权重高的分到的请求多。适合后端机器配置异构的场景,比如 web01 是 8 核,web02 是 4 核,可以把权重设为
2:1。这也正是我上面配置示例里使用的模式。 - SH(Source Hashing):源地址哈希,对客户端 IP 做哈希计算后分配到固定的 RS。同一个客户端的请求永远到同一台服务器,天然实现会话保持。适合需要保存客户端本地状态的老式应用。
- DH(Destination Hashing):目标地址哈希,按请求的目标地址做哈希,一般用在多级缓存架构里,把相同内容的请求固定到同一台缓存服务器。
5.2 动态调度算法
动态算法会实时收集 RS 的连接数,然后根据当前负载做分配:
- LC(Least Connections,最少连接):把新请求分配给当前连接数最少的 RS。适合长连接请求占比较高的场景,比如 WebSocket、数据库连接池。
- WLC(Weighted Least Connections):LC 的加权版本,公式是
(活动连接数 + 1) / 权重,取最小值的那台 RS。这是我个人在生产环境里最常用的算法,因为它既考虑了后端差异,又兼顾了实时负载。 - SED(Shortest Expected Delay):计算公式略有区别,是
(活动连接数 + 1) * 256 / 权重,倾向把请求分给权重高但当前连接少的机器。适合请求耗时不均匀的场景。 - NQ(Never Queue):SED 的升级版,如果所有 RS 都有连接,它会找一台空闲的连接数最少的;如果存在完全空闲的 RS,直接分配过去,避免排队。
5.3 会话保持的两种思路
HTTP 协议本身是无状态的,但业务往往需要会话保持,典型场景就是用户登录后,Session 存在了某台服务器的本地内存里,下一次请求被调度到其他服务器就找不到了。
解决这个问题有两层思路:
第一层是 LVS 这一层解决。如果后端应用真的改不动,可以用 SH 算法,让同一来源 IP 的请求始终落在同一台 RS。但这个方法在 NAT 出口下面效果很差,比如一个大楼的所有用户出口 IP 都一样,流量全部堆到一台 RS,等于负载均衡失效。
第二层是应用层解决。把 Session 抽出来放到 Redis 或 Memcached 里,让所有 RS 共享。这是目前主流架构的标准做法。我对任何团队的公开建议都是优先做应用层会话共享,而不是在 LVS 层靠哈希硬撑,因为后者只是掩盖了架构问题。
5.4 我的算法选择建议
给一个可以直接抄作业的结论:
| 业务场景 | 推荐算法 | 理由 |
|---|---|---|
| 后端配置完全相同的无状态服务 | RR | 简单高效,无倾斜风险 |
| 后端配置有差异的无状态服务 | WRR | 权重可控 |
| 后端性能差异大、连接时长不均匀 | WLC | 自动按实时连接数分配 |
| 老系统必须做会话保持 | SH | 同 IP 固定到同机器 |
| 长连接 / WebSocket | LC / WLC | 避免连接数堆积 |
6. 给 LVS 加上高可用:Keepalived 与故障自动转移
6.1 单点故障是 LVS 最大的敌人
LVS 本身性能再强,也改变不了它是一个单点的事实。如果 LB 这台机器挂了,整个集群对外入口全部瘫痪,RS 再多也无济于事。这跟“把鸡蛋放在一个篮子里”是一个道理。所以生产环境的 LVS 一定是多台组成主备,配合 VRRP 协议做故障转移。
Keepalived 是当前最主流的方案。它提供两台核心能力:一是 VRRP 协议实现 IP 漂移,也就是让 VIP 在主备之间切换;二是健康检查脚本,定时探测后端 RS 的存活状态,发现异常自动从 IPVS 规则里摘除。
6.2 Keepalived 配置实战
假设我有两台 LB:lvs01(192.168.1.10)作为 MASTER,lvs02(192.168.1.13)作为 BACKUP。
先安装 Keepalived:
bash复制yum install -y keepalived
MASTER 上 /etc/keepalived/keepalived.conf 的核心配置:
code复制global_defs {
router_id LVS_MASTER
}
vrrp_instance VI_1 {
state MASTER # BACKUP 节点要改成 BACKUP
interface eth0 # 绑定 VIP 的网卡
virtual_router_id 51 # 主备必须一致
priority 100 # MASTER 优先级高于 BACKUP
advert_int 1 # VRRP 通告间隔,单位秒
virtual_ipaddress {
192.168.1.100/24 dev eth0 label eth0:0
}
}
virtual_server 192.168.1.100 80 {
delay_loop 6 # 健康检查间隔,单位秒
lb_algo wrr # 调度算法
lb_kind DR # 工作模式
persistence_timeout 0 # 连接保持时间,0 表示不保持
real_server 192.168.1.11 80 {
weight 1
HTTP_GET {
url {
path /
status_code 200
}
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
real_server 192.168.1.12 80 {
weight 2
HTTP_GET {
url {
path /
status_code 200
}
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
}
重点解释几个容易踩坑的配置项:
virtual_router_id:主备必须一致,范围 0~255。如果你在同一个局域网里有多个 Keepalived 集群,这个 ID 不能重复。priority:MASTER 建议 100,BACKUP 建议 90。如果主备优先级一样,VIP 会飘忽不定。persistence_timeout:如果设置了大于 0 的值,那么来自同一个 IP 的请求在超时时间内都会被转发到同一台 RS。这个参数在某些场景下是救命稻草,但默认 0 就好,不要随意设置。HTTP_GET健康检查:Keepalived 会定期向 RS 发 HTTP 请求,只要返回 200 就认为是健康的。如果你的业务健康检查路径不是根路径,改成实际的健康检查路由。
然后启动服务并验证:
bash复制systemctl enable keepalived
systemctl start keepalived
# 检查 VIP 是否绑定成功
ip addr show eth0
# 检查 IPVS 规则是否被 Keepalived 自动加载
ipvsadm -L -n
6.3 故障转移验证
这一步必须实测,不要等到真的出故障了才验证。模拟方式很简单:
- 在 lvs01 上执行
systemctl stop keepalived。 - 几秒钟内,VIP 应该从 lvs01 漂移到 lvs02。在 lvs02 上执行
ip addr show eth0,能看到 192.168.1.100 出现在 eth0 上,同时 lvs02 的 IPVS 规则自动接管。 - 客户端继续访问 VIP,HTTP 请求无感知。
- 再把 lvs01 的 keepalived 启动,VIP 会重新漂移回 MASTER。
再模拟 RS 故障:
- 在 web01 上执行
systemctl stop nginx。 - 等待
delay_loop指定的秒数(我配置的是 6 秒),Keepalived 健康检查失败。 - 执行
ipvsadm -L -n,你会发现 web01 已经从 RS 列表里被移除。 - 客户端访问 VIP,流量只会到 web02,服务不中断。
7. 生产环境踩坑记:ARP 冲突、延迟抖动与会话风暴
7.1 故障一:RS 对外通告了 VIP,导致 LB 收不到流量
现象:所有配置看着都对,RS 上的 Nginx 也正常,但 curl VIP 要么超时,要么时通时不通。
排查过程:我在 LB 上用 tcpdump -i eth0 host 192.168.1.100 抓包,发现 VRRP 通告正常,但来自客户端的 SYN 包根本没到达 LB。问题定位到了交换机上——交换机上已经悄悄把 VIP 对应的 MAC 地址解析成了某台 RS 的 MAC,请求直接转发到了 RS,完全绕过了 LB。
根因:RS 上绑定 VIP 时,用了 arp_ignore 参数但配置只写了 all,没写 lo,或者写错了配置文件,导致 RS 的 lo 接口仍然响应 ARP 请求。
解决办法:
bash复制echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce
这里 arp_ignore=1 的含义是“只回答目标 IP 是本接口 IP 的 ARP 请求”,VIP 绑定在 lo 上,网卡 eth0 的 IP 不是 VIP,所以 eth0 不会响应 VIP 的 ARP 请求;arp_announce=2 的含义是“发送 ARP 通告时,始终使用接口上配置的最佳 IP,而不使用其他接口的 IP”。这两个参数组合是 DR 模式的标准配置。
7.2 故障二:keepalived 脑裂
现象:两台 LB 同时绑定了 VIP,此时从客户端访问 VIP,会有一半概率连到错误的 LB,整个集群的负载均衡行为完全不可预测。
根因:VRRP 通告被防火墙拦了,或者主备之间的组播网络异常,导致 BACKUP 以为 MASTER 死了,自己抢了 VIP。我在一次机房网络割接后就遇到过这个问题,防火墙策略变更后,VRRP 的组播报文被丢弃。
解决与预防:
tcpdump -i eth0 vrrp确认 VRRP 报文是否正常收发。- 检查防火墙是否放行 VRRP 协议(IP 协议号 112)。
- 在多网卡上做好 VRRP 报文绑定的接口,确保通知隔离。
- 最好部署第三方检测脚本,比如通过 API 探测 VIP 的归属,一旦发现双主就告警。
7.3 故障三:连接超时与 TIME_WAIT 堆积
现象:压测时并发量一上来,客户端大量连接超时,LB 上 TIME_WAIT 连接数暴涨。
排查:LVS 默认对 TCP 连接在空闲超时时间内不做主动断开,如果客户端或后端没有正确关闭连接,连接会长期挂起,最终 LD 的 conntrack 表被填满,新连接无法建立。
解决:
- 在内核参数里调整 conntrack 表大小:
bash复制net.netfilter.nf_conntrack_max = 655360
net.netfilter.nf_conntrack_tcp_timeout_established = 600
- 在 RS 上调整 Nginx 的 keepalive 超时,避免长连接空闲时间过长。
- 如果业务并发确实很高,考虑使用 DPDK 版本的 DPVS,或者直接上云负载均衡。
7.4 故障四:健康检查误杀
现象:RS 实际业务正常,但 Keepalived 的健康检查一直失败,把 RS 从集群里摘除了。
根因:我最初用 TCP 健康检查,后来改成了 HTTP_GET,但健康检查的 URL 路径没有放到应用层的白名单里,导致返回 403,检查失败。另外,connect_timeout 设置太短(1 秒),后端口偶发 1.5 秒才响应,就直接判死了。
解决:健康检查的路径最好是独立的轻量接口,返回 200 即可;超时时间不要太激进,我一般设置 connect_timeout 3、nb_get_retry 3。如果接口偶发超时是因为数据库慢查询,先解决慢查询,而不是调大超时来掩盖问题。
8. 从 LVS 出发,再往前走一步
LVS 这套方案,我用了几年下来,最大的感受是:它是一个“用得越久越明白其精妙”的基础设施。但也要承认,在当前云原生时代,LVS 的定位已经不再是什么都要往前冲的独角兽,而是作为整个流量入口体系中的一个高可用底座。
我见过不少团队,把 LVS 和 Nginx 混在一起用,以为有了 LVS 就不需要 Nginx 了。实际上两者根本不是替代关系,而是各司其职:LVS 负责四层海量并发接入,Nginx 负责七层路由、限流、缓存、SSL 卸载。架构上常见的是:客户端 -> LVS -> Nginx 集群 -> 业务服务。你甚至可以只做两层:LVS 直接转发到业务应用,前提是你的业务不依赖七层路由能力。
另外,关于“LVS 是不是落后了”这个问题,我的观点是:它确实不算新潮,但它解决的是所有业务都绕不开的高并发问题,而且是经过十几年生产验证的最稳方案之一。云厂商的 SLB 底层很多也是类似 LVS 的内核态转发模型,只是把运维和弹性能力做成了黑盒给你用。对自建机房、混合云、私有化部署这些场景,LVS 依然是一等一的选择。
如果你正在从单机走向集群,我建议按这个顺序学习:先把 LVS 的 DR 模式手工部署通,理解数据帧的流转路径;然后配上 keepalived,把主备和健康检查玩明白;最后再考虑调度算法和调优。这套链路走完,你对整个网络栈的理解会比大多数只点按云服务的人深不止一个层次。
