前阵子有个朋友在群里问:“LVS到底还值不值得学?现在云原生一堆网关,是不是不用折腾这个了?”我的回答一直是:值得,而且很多大流量入口现在还在用LVS扛着。Linux Virtual Server这套东西从1998年走到今天,靠的不是情怀,而是它内核态转发带来的极致性能。这篇文章我打算把LVS(Linux Virtual Server)负载均衡器的原理、三种工作模式、调度算法、完整配置和排障思路从头到尾过一遍。尤其会重点聊聊被问得最多的空闲超时问题——很多人配置好后线上频繁断连,十个里有八个是栽在timeout上。不管你是刚接触四层负载均衡的运维新人,还是准备在生产环境正式落地LVS的进阶选手,这篇文章都能给你一个可以直接参考的完整路线。
1. LVS整体设计与方案选型思路
1.1 LVS是什么,它到底解决了什么问题
LVS全称Linux Virtual Server,是一套基于Linux内核实现的服务器集群方案。它的核心逻辑就一句话:对外提供一个虚拟IP(VIP),把所有请求通过内核中的IPVS(IP Virtual Server)模块,按预设的调度算法转发到后端的真实服务器(Real Server,简称RS)上。
它工作在OSI模型的第四层,也就是传输层,只看TCP/UDP的IP加端口,不关心HTTP头、URL、Cookie这些七层内容。这个特点既是它的“边界”,也是它的优势。因为不需要解析应用层数据,所以LVS的处理路径极短:数据包从网卡进来,在内核里直接改写目的地址或MAC后发出去,根本不经过用户态拷贝。对比Nginx、HAProxy这类工作在应用层的软件,LVS在同等硬件条件下能支撑的并发连接数要高出一个量级,这也是大型网站至今仍把LVS放在最前端的原因。
它解决的核心问题有三个:一是高性能,单机可以支撑百万级别并发连接;二是高可用,结合Keepalived可以实现Director节点故障自动切换;三是高扩展,后端RS可以随时加减,容量不够就加机器,对客户端完全透明。
1.2 三种工作模式怎么选
LVS有三种包转发模式:NAT、DR、TUN。理解这三者的区别,是后续配置不出错的前提。
NAT模式(VS/NAT)最直接:Director收到客户端请求后,通过DNAT把包的目的IP改成选中的RS地址,RS处理完再把响应回给Director,由Director改写源IP后转回客户端。这样来回流量都要过Director,Director很容易成为瓶颈。但好处是RS可以跨网段,RS的网关指向Director就行,架构简单,适合小规模场景。
DR模式(VS/DR)是生产中用的最多的方案。Director在转发时只改数据链路层的目标MAC地址,把帧直接发给RS,网络层的IP完全不变。RS在回环接口上绑定VIP,收到数据后发现自己就是目标地址,于是正常处理,响应时直接以VIP作为源地址回给客户端,走的是原路返回,完全不经过Director。这意味着入向流量走Director,出向流量各回各家,Director的压力小了很多,性能最好。代价是Director和RS必须在同一个二层网络里,而且RS必须做ARP抑制,否则客户端ARP请求VIP时会同时收到多台机器的响应。
TUN模式(VS/TUN)是把请求包用IP隧道封装后送给RS,RS解封装处理,响应直接回客户端。它兼顾了NAT的跨网段能力和DR的吞吐性能,但要求RS支持隧道协议,配置和排障复杂度都高一些,实际中用得不如DR普遍。
放在一起看:
| 对比项 | NAT | DR | TUN |
|---|---|---|---|
| 转发方式 | 修改目的IP | 修改目标MAC | IP隧道封装 |
| 响应路径 | 必须经过Director | RS直接回客户端 | RS直接回客户端 |
| 网络要求 | 可跨网段,RS网关指Director | 必须同二层网络 | 可跨网段,需支持隧道 |
| Director压力 | 大,容易成瓶颈 | 小,理论性能最优 | 较小 |
| 配置复杂度 | 低 | 中,要处理ARP | 中高 |
| 适用场景 | 小规模、跨网段 | 同机房大流量首选 | 跨机房、异构网络 |
我的经验是:同机房场景无脑优先DR,反正现在机房里的RS基本都在同一个交换机下面,DR的性价比最高;只有RS分散在不同网段、又不想引入更复杂的方案时才考虑NAT或TUN。
1.3 为什么现在还要选LVS
很多人纠结:Nginx也能做负载均衡,为什么要多套一层LVS?我的理解是,它们根本不是竞争关系,而是上下游关系。
Nginx工作在七层,能拿到HTTP完整信息,适合做路由分发、缓存、限流、WAF这类精细化控制;但七层解析本身就消耗CPU,并发上来之后性能下降明显。LVS工作在四层,逻辑简单、转发快,适合挡在最前面处理海量TCP连接,把流量“粗分”给后面的七层集群。
于是就有了非常经典的“四层+七层”分层架构:客户端 -> LVS(四层负载均衡) -> Nginx集群(七层反向代理) -> 业务服务器。LVS负责扛流量,Nginx负责做业务路由,各干各的活,谁也不抢谁的资源。这个架构我现在搭系统还在用,实测在高并发场景下比单纯堆Nginx稳定得多。
2. 核心细节解析:调度算法与关键参数
2.1 静态调度算法有哪些
LVS内置了十种调度算法,先看静态的几种。
RR(Round Robin)就是轮流分发,每个请求按顺序分给下一台RS,大家机会均等。前提是RS的硬件配置、处理能力都差不多,否则弱的机器会先被打垮。
WRR(Weighted Round Robin)在RR基础上加了权重,权重越高的RS收到的连接越多。比如8核和16核的机器权重可以配成1和2。权重不是越高越好,要根据实际处理能力压测得出,拍脑袋配权重照样会把机器压垮。
SH(Source Hashing)按客户端源IP做哈希,同一个源IP的请求会固定落到同一台RS上,天然实现了会话保持,适合那些不方便用Cookie做会话粘滞的协议。但缺点也明显:某台RS挂了,哈希到它的那批客户端全部受到影响,会话集中失效的风险比较大。
DH(Destination Hashing)按目的IP做哈希,多用于多台防火墙或缓存服务器的场景,按目标地址把流量分流。日常业务负载均衡里用得少。
2.2 动态调度算法怎么选
动态算法的共同点是:每次调度时都会去看后端的实时负载状态,最常见的指标是连接数。
LC(Least Connections)把新请求分给当前活跃连接数最少的RS。这个算法在长连接场景下比较准,但在短连接风暴下,活跃连接数跳动很快,调度可能不够均匀。
WLC(Weighted Least Connections)在LC基础上结合了权重,也是LVS的默认调度算法。计算公式是:当前连接数除以权重,取结果最小的RS。它兼顾了静态权重和动态负载,大部分业务场景下用它都不会出大问题。
SED(Shortest Expected Delay)在WLC基础上加了“延迟预估”,优先选连接数除以权重后预期延迟最小的RS。NQ(Never Queue)是SED的改进版,保证不会有RS一直处于空闲排队状态,适合响应速度差异较大的后端。
LBLC(基于局部性的最少连接)和LBLCR(带复制的局部性最少连接)是专门给缓存服务器设计的。前者让相同目的IP的请求尽量走同一台缓存RS,提高缓存命中率;后者允许缓存复制到多台RS,容忍单点故障。普通业务用不上,但如果你是做缓存网关,这两个算法值得研究。
MH(Maglev Hashing)是后来加入的一致性哈希算法,它解决的是SH在RS增删时大量会话迁移的问题:只有少量连接会重新映射,适合需要平滑扩缩容的场景。
2.3 我常用的算法选型参考
算法这个东西,没有绝对的对错,只有适不适合。我整理了这份参考:
- 后端配置均衡、无特殊要求:直接默认WLC,省心。
- 需要会话保持、后端又不支持应用层粘滞:SH或带持久性参数的WLC。
- 长连接为主(数据库连接池、WebSocket):LC或WLC。
- 短连接高并发(HTTP接口):WLC配合合理的超时参数。
- 缓存集群:LBLC或LBLCR。
- 扩缩容频繁、尽量少的会话迁移:MH。
另外提醒一句:调度算法解决的是“转发给谁”的问题,会话保持不能只靠算法。生产环境里我通常会配合Keepalived的persistence_timeout一起用,双保险。
3. 实操过程与核心环节实现
3.1 部署前要准备什么
以DR模式为例,假设我有两台RS,一台Director,都跑在同一个网段192.168.100.0/24:
- Director:192.168.100.10
- RS1:192.168.100.11(权重1)
- RS2:192.168.100.12(权重2)
- VIP:192.168.100.100
第一步是确认内核有没有IPVS模块。LVS的核心功能都在内核里,目录系统一般默认编译了ip_vs模块,但保险起见还是手动加载一下:
modprobe ip_vs modprobe ip_vs_wlc modprobe ip_vs_sh modprobe ip_vs_mh lsmod | grep ip_vsip_vs加载成功后会显示一批模块。接着安装用户态管理工具:
# Debian/Ubuntu apt install ipvsadm # CentOS/RHEL yum install ipvsadm如果ipvsadm -L -n执行时报“No such file or directory”,基本就是ip_vs模块没加载,回去加载模块再试。
3.2 DR模式完整配置步骤
先配置Director。把VIP配置到物理网卡上,然后添加虚拟服务和两台RS:
ip addr add 192.168.100.100/32 dev eth0 ipvsadm -A -t 192.168.100.100:80 -s wlc ipvsadm -a -t 192.168.100.100:80 -r 192.168.100.11:80 -g -w 1 ipvsadm -a -t 192.168.100.100:80 -r 192.168.100.12:80 -g -w 2这里的要点:
-A表示添加虚拟服务,-t指定TCP协议加VIP端口,-s指定调度算法。-a添加RS,-r指定RS地址,-g代表DR模式(gateway),NAT模式用-m,TUN模式用-i。-w配权重。- VIP用/32掩码,因为DR模式下VIP不应该出现在路由表中参与网段广播。
然后是两台RS上的配置。RS要把VIP绑定到回环接口上,同时必须做ARP抑制,否则客户端ARP广播问VIP时,RS也会应答,流量就不经过Director了:
ip addr add 192.168.100.100/32 dev lo 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_announcearp_ignore设为1,表示只应答目标IP是本机且属于到达网卡接口的ARP请求;arp_announce设为2,表示ARP响应使用源IP所在网卡的最佳本地地址。两行配合,RS就不会对外宣告自己有VIP了。这些配置要写进/etc/sysctl.conf和开机脚本里,别重启机器就失效。
现在从客户端访问http://192.168.100.100,请求会被转发到RS1或RS2。验证一下:
ipvsadm -L -n输出里能看到虚拟服务和两台RS的状态,ActiveConn和InActConn会随着访问增长。再开一个终端跑ipvsadm -L -n -c,能看到连接表里每一条转发记录的来源IP、目的IP和当前状态,排障时这个命令是黄金工具。
3.3 用Keepalived管好健康检查和高可用
裸配ipvsadm的问题有两个:一是RS挂了LVS不会感知,请求照样转发过去,用户直接报错;二是Director自身单点,Director挂了整个集群就瘫了。这两件事都交给Keepalived解决。
Keepalived同时承担两个角色:VRRP负责Director高可用(两个Director抢一个VIP),IPVS管理负责维护LVS规则和RS健康检查。配置核心部分如下:
vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 192.168.100.100/24 dev eth0 } } virtual_server 192.168.100.100 80 { delay_loop 6 lb_algo wlc lb_kind DR persistence_timeout 50 protocol TCP real_server 192.168.100.11 80 { weight 1 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } real_server 192.168.100.12 80 { weight 2 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } }几个配置的坑我得单独说:
persistence_timeout是持久连接超时,单位秒。它保证来自同一个源IP的请求在50秒内都发往同一台RS,对不带会话的应用来说这是最简单的会话保持方案。但它也有副作用——某个源IP流量特别大时,会长时间压在单台RS上,导致负载不均。我见过有人把persistence_timeout设成3600,结果一台机器被打爆另一台闲着。默认值或略高即可,除非业务确实需要长时间粘滞。
TCP_CHECK是Keepalived在Director上主动向RS发起TCP连接探活。这里有个容易忽略的细节:如果RS上对应端口只允许特定来源访问,探活会失败,Keepalived会把健康RS误判为宕机。我踩过这种坑,后来习惯在RS的iptables里单独放行Director的探活IP。
另外Keepalived里的lb_kind DR和-g是对应的,如果用了NAT模式记得改成NAT。两个Director的配置除了state和priority不同,其他要保持一致,否则VRRP会出现脑裂。
配置完在Director上启动Keepalived:
systemctl enable --now keepalived此时LVS规则由Keepalived自动维护,不再需要手动执行ipvsadm命令。用ipvsadm -L -n还能看到规则,但增删由Keepalived管理。
3.4 权重和容量怎么定
权重不是拍脑袋定的。我一般先做压测:分别压RS1和RS2,得到单机QPS上限,然后按QPS比例反推权重。比如RS1压测上限是8000 QPS,RS2是16000 QPS,权重就配1比2。权重真需要调整时,用以下命令在线修改,不用重启服务:
ipvsadm -e -t 192.168.100.100:80 -r 192.168.100.11:80 -g -w 3容量规划上,DR模式下Director只处理入向请求,理论上单台Director可以支撑的并发连接数主要受内存限制。每个连接条目在内核里占的内存很小,4GB内存的Director扛几十万并发连接是常见的。但别忘了给Director留出足够的内核连接跟踪表空间,必要时调大net.netfilter.nf_conntrack_max,否则连接数上来之后新连接会被丢弃,表现就是莫名其妙的超时。
4. 空闲超时问题深挖:线上断连的头号元凶
4.1 空闲超时是怎么发生的
被问得最多的问题就是:“我的WebSocket连上之后,过一会儿就被断了,为什么?”
这就要说到LVS的连接表超时机制。IPVS在内核里维护一张连接表,记录每一条转发连接的元数据。一条连接如果空闲太久,IPVS会认为它已经结束了,把表项清掉。默认的清理时间是:
ipvsadm -L --timeout输出结果一般是这样:
Timeout (tcp tcpfin udp): 900 120 300TCP空闲900秒(15分钟)、TCP FIN状态120秒、UDP空闲300秒。如果一条TCP连接在15分钟内没有数据包经过LVS,连接表项就会被回收。问题在于:表项被回收后,客户端和后端其实还在维持着这条连接(比如WebSocket的ping/pong间隔超过了15分钟,或者某些长连接应用根本不发心跳)。当下一个数据包到达LVS时,LVS查不到对应表项,只能把它当作一条新连接重新调度,结果包被发到了另一台RS上,甚至直接被丢弃,原连接就断了。
还有一种常见场景是UDP:DNS、NTP这类短请求无所谓,但部分基于UDP的实时通信服务,300秒超时远不够,一旦空闲超过5分钟就掉线。
4.2 如何调整超时参数
调整方法很简单:
ipvsadm --set 3600 120 300这会把TCP空闲超时改为3600秒,TCP FIN超时120秒,UDP空闲超时300秒。TCP FIN保持120秒没问题,UDP如果有长时间无数据的业务,也要相应调大。
但这里我要多说一句:超时改大不是没有代价的。连接表项是内存资源,超时越长,表里堆积的死连接越多。如果业务大部分是短连接、又开着很长的TCP超时,表项会被大量无效连接占满,新连接反而无法建立。商城里卖的“统一改大超时”方案,真到线上是要出事的。正确姿势是:
- 短连接业务:保持默认900秒就好,别动。
- 长连接、有心跳的业务:把TCP超时调到比心跳间隔大2到3倍,比如心跳30秒,超时设90秒足够,没必要上3600。
- 无心跳但长时间空闲的连接:首先考虑在应用层加心跳,而不是无限调大LVS超时。
另外Keepalived的persistence_timeout会和这个超时产生叠加效果。持久连接模板的超时控制的是“同一个源IP是否继续粘滞到同一台RS”,它到期后连接不会被断开,只是重新参与调度。所以如果你的会话保持失效、出现请求跑到另一台RS的情况,查的不是ipvsadm --set,而是persistence_timeout。
4.3 长连接场景的更多处理技巧
除了调超时,还有几个内核参数能优化长连接场景。
expire_nodest_conn:默认0。如果设为1,当RS不可用(被Keepalived摘除)时,连接表里指向该RS的表项会被立即清理,而不是干等到超时。这样RS恢复后,老连接不会继续被错误地转发过去。我建议开启:
echo 1 > /proc/sys/net/ipv4/vs/expire_nodest_connexpire_quiescent_template:Keepalived把RS标记为“静止”状态时,是否清理对应的持久连接模板,默认0。如果希望摘除RS后,原先粘滞到它的客户端马上被重新散列到别的RS,就把它设成1。
sloppy_tcp:允许IPVS在连接状态不完整时继续转发TCP包。如果你的环境里出现过连接表不同步、重启Director后大量连接异常的情况,这个开关能减少连接中断,但也会削弱对异常包的过滤能力,生产环境谨慎开。
对于WebSocket这类需要长时间维持连接的业务,除了调大LVS超时,我还会在应用层加心跳,并且把心跳间隔控制在30到60秒。这样即使未来LVS超时配置被重置或迁移,也不至于两分钟就断连。
5. 常见问题与排查技巧实录
5.1 典型报错速查表
我把自己和同行遇到的高频问题整理成了表,方便遇到问题时直接对号入座。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| ipvsadm -L -n报“No such file or directory” | ip_vs模块未加载 | modprobe ip_vs,重新执行 |
| 客户端访问VIP不通 | Director上VIP没配或Keepalived没起来 | 检查ip addr和keepalived状态 |
| 后端RS能通,但请求总打到固定一台RS | 调度算法是SH,或persistence_timeout过大 | 换WLC,调小持久超时 |
| RS已经被摘除,请求还是过去 | 健康检查未生效,或摘除前连接表还有残留 | 确认TCP_CHECK配置,开启expire_nodest_conn |
| 客户端偶尔超时,连接瞬间断开 | 空闲超时过短,表项被回收 | 按4.2节调整ipvsadm --set |
| 多台RS都在回ARP,RS收不到包或Director和RS抢VIP | 没有做ARP抑制 | 按3.2节设置arp_ignore/arp_announce |
| Director性能不错但吞吐上不去 | 网卡中断不均、连接跟踪表满 | 开网卡多队列,调大nf_conntrack_max |
| Keepalived主备一直在切 | VRRP配置不一致或网络丢包 | 对比vrrp_instance配置,检查advert_int超时 |
5.2 用连接表定位问题的思路
排查LVS问题,我有一套固定的操作顺序。先用ipvsadm -L -n确认规则在不在,再看连接表:
ipvsadm -L -n -c连接表里每行都有连接状态。TCP场景下如果看到大量SYN_RECV状态堆积,说明包到了LVS但RS没有正确回应,赶紧去查RS的应用端口和防火墙。如果看到NONE状态很多,多半是UDP转发或者连接已经被回收。
再看统计数据:
ipvsadm -L -n --stats ipvsadm -L -n --rate--stats看累计包数、字节数、连接数;--rate看每秒速率。通过对比两台RS的流量是否按权重比例分配,能快速判断调度是否正常。
如果确认LVS规则正常但请求还是不通,用tcpdump在Director上看包有没有进来,再到RS上看包有没有到达:
tcpdump -i eth0 host 192.168.100.100 and port 80包到了RS但应用没反应,问题在RS;包根本没到RS,问题在网络或LVS转发路径上。用这个方式一步步缩小范围,比瞎猜效率高得多。
5.3 我踩过的三个实战坑
第一个坑是DR模式下忘了关RS的rp_filter反向路径过滤。某些发行版默认开启了rp_filter,RS收到VIP的数据包后,发现源路由和到达接口不一致,直接丢弃,表现就是LVS转发正常但服务一直超时。解决办法是确认RS网卡配置里net.ipv4.conf.all.rp_filter和net.ipv4.conf.<网卡>.rp_filter为0或者2。
第二个坑是Keepalived的virtual_ipaddress配置里忘了指定网卡。默认它会把VIP挂在默认路由对应的网卡上,你要是服务器有多个网卡,VIP可能被挂到了业务流量根本不走的接口上,导致VIP能ping通的人和真正访问你的人不是同一条路径。后来我所有VIP配置都显式写成192.168.100.100/24 dev eth0,再没出过这事。
第三个坑是线上扩容时直接对RS执行ipvsadm -a添加新机器,忘了在RS上做ARP抑制。新RS一上线,整个集群的VIP ARP响应被打乱,客户端部分流量直连新RS,全部转发绕过Director,问题持续到大半夜才定位到是ARP广播冲突。扩容操作前一定把RS侧配置检查清单过一遍,尤其是ARP和sysctl项,宁可慢一点也不要带病上线。
6. 生产环境落地的一点个人体会
LVS配置本身并不复杂,复杂的是你对自己业务的流量模型有没有清晰的认知。是短连接还是长连接,需不需要会话保持,后端机器能力是否均衡,这些问题的答案直接决定了调度算法、超时参数和持久化时间该怎么配。我每接一个项目,第一件事永远是梳理业务流量特征,再动手写配置,而不是把上一套架构的配置原封不动复制过来。
最后再分享一个实用习惯:我会把所有LVS相关参数(算法、超时、权重、持久化时间、内核开关)写进配置管理里,并标注清楚每个参数的调整理由。这样每次变更都有据可查,线上出问题回滚也快。负载均衡是流量入口,改动影响面极大,宁可多花时间做预案,也不要在半夜靠手速救命。希望这篇文章能让你少踩几个我当年踩过的坑。