你们有没有遇到过这种情况?同一套业务系统,单台服务器CPU动不动飙到90%,扛不住大促流量;数据库、缓存、应用服务堆在一起,扩容只能靠换更大的机器。很多人第一反应是上Nginx做反向代理,但等流量再涨一个量级,Nginx本身反而成了瓶颈。我在生产环境里折腾过不少方案,最后沉淀下来最稳、也最值得花时间吃透的,还是Linux虚拟服务器,也就是大家常说的LVS。
LVS(Linux Virtual Server)是一套内核态的负载均衡方案,由章文嵩博士主导开发,从Linux 2.4内核开始就内置在系统里。它的核心思路很简单:在一台Director服务器上把进来的流量按照某种策略分发到后端的Real Server,再用某种方式让响应数据回到客户端。因为工作在TCP/IP协议栈的四层,转发性能极高,一台普通的双核机器也能扛住每秒几万甚至十几万的连接请求。这篇文章我会从工作模式选型、核心细节、高可用设计、实际搭建步骤、常见故障排查几个方面,把LVS从原理到落地完整梳理一遍,适合正在做集群架构、或者打算用LVS替换Nginx做入口负载的同学参考。
1. 从三层模式说起:为什么我不推荐NAT
1.1 LVS三种工作模式的区别
LVS一共有三种工作模式:VS/NAT、VS/TUN、VS/DR。很多同学刚接触的时候容易混淆,我先把它们的核心差异讲透。
VS/NAT模式下,Director作为网关角色,请求进来后它会改目标IP和端口,把包转发给后端的Real Server。Real Server处理完把响应返回给Director,Director再改源IP回给客户端。整个请求和响应都经过Director,所以Director会成为瓶颈,而且Real Server的网关必须指向Director,网络拓扑限制比较大。这种模式适合后端机器和Director在同一个内网网段的小规模场景。
VS/TUN模式把请求包封装在IP隧道里发给后端,后端响应直接走独立路由回给客户端,要求每台Real Server支持隧道协议,配置复杂,运维成本高。
VS/DR模式是生产环境用得最多的。请求进来后,Director只修改二层帧头的目标MAC地址,把帧转发给挑选出来的Real Server,Real Server收到后发现IP头目标就是自己的VIP,于是处理请求并把结果直接通过自己的物理网卡回给客户端。响应流量不经过Director,所以Director的压力小很多,能支撑更大的并发。DR模式要求Director和Real Server在同一个二层网络,且Real Server的VIP不能响应ARP广播。
1.2 生产选型:为什么首选DR模式
我在实际项目里绝大多数情况都用DR模式,原因很简单:性能上限高、配置相对可控、不依赖额外协议。比如一个电商系统的首页门户,前面挂两台LVS(一主一备),后端是五台Nginx承担静态资源和反向代理,再往后才是Spring Boot服务。在这个架构里,LVS只负责按VIP把流量分给Nginx,Nginx再把请求代理到应用层,两种负载各司其职。如果全用Nginx做入口,当连接数上来以后,进程切换和代理开销会吃掉不少CPU,而LVS在内核里就完成了选路,几乎不产生用户态开销。
当然,DR模式也有个天然限制:必须能修改目标MAC,所以和后端机器必须在同一个广播域。跨机房跨VPC的场景就不适合了,这种情况你可以考虑用DNS轮询或者云厂商的四层SLB。选型没有绝对的最优解,关键看你的网络条件。
1.3 关于LVS和Nginx、HAProxy的关系
很多人问LVS是不是已经被Nginx替代了。我的经验是:它们根本不是一个层面的东西。LVS工作在四层(传输层),基于内核的ipvs框架做转发,不解析HTTP头部,不认识URL路径和后端业务逻辑。Nginx工作在七层(应用层),能根据Host、URI、Cookie做精细化路由。HAProxy介于两者之间,既能做四层也能做七层,但性能不如LVS的纯内核转发。
正常的生产链路是:LVS做第一层入口,保证高并发和可用性;Nginx或HAProxy做第二层,负责请求路由和缓存策略。一个负责量大管饱,一个负责精致分发,两者搭配才是成熟的集群方案。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. DR模式核心细节:ARP抑制和调度算法
2.1 一台机器上到底有几个IP
讲DR模式之前,先把几个IP角色理顺。VIP是虚拟IP,也就是对外提供服务的IP,客户端访问的就是它。DIP是Director的物理网卡IP,用来和后端联动。RIP是Real Server的物理网卡IP,也是后端真实业务服务的地址。在DR模式下,VIP同时配在Director和每一台Real Server的loopback接口上,但只有Director才会对VIP的ARP请求做出响应。
这里有同学会懵:每台后端都配VIP,那客户端拿到VIP的MAC地址会不会乱?所以必须做ARP抑制,让Real Server的VIP不响应ARP广播,只让Director响应。这样客户端发往VIP的流量只会进Director,再由Director改写MAC后转发给对应的Real Server。
2.2 ARP抑制的具体配置
Real Server上要加两条sysctl配置,分别调整arp_ignore和arp_announce。arp_ignore设为1,表示ARP请求的目标IP如果是本机IP,且这个IP在接收请求的网卡上,才响应。arp_announce设为2,表示发送ARP通告时尽量使用能到达目标的最优本地地址,避免把VIP所在网卡的地址通告出去。
实际操作时,在每台Real Server的/etc/sysctl.conf里追加内容,然后执行sysctl -p使其生效。这么做的本质,是防止后端多台机器同时响应VIP的ARP请求,保证VIP的MAC地址始终指向Director,流量才能按预期被导演。你别小看这几行参数,我见过不少集群异常,页面一会儿通一会儿不通,最后查下来就是ARP没抑制好。
2.3 ipvsadm支持的调度算法怎么选
LVS调度算法比较常用的是rr、wrr、lc、wlc。rr是轮询,每台机器轮着来,适合后端机器性能差不多的场景。wrr是加权轮询,可以给高配机器更高的权重。lc是最少连接,谁连接少分给谁;wlc是加权最少连接,综合权重和连接数。生产中使用wrr最普遍,因为你后端机器往往新旧混跑,比如一台4核8G,一台8核16G,你给高配机器weight设2,低配设1,流量分配就比较合理。
还经常被忽略的是sh(源地址哈希)和dh(目标地址哈希)。sh能把相同源IP的请求固定分到同一台Real Server,用于需要保持会话的场景。但LVS本身不维护会话,如果真的需要session保持,传统做法是后端共享session存储,比如Redis,比依赖负载均衡器的hash更可靠。我在设计系统时一般不把会话状态放在Web层,而是独立出Session服务,这样负载均衡策略可以随便调。
3. 高可用设计:LVS+Keepalived是目前最省心的组合
3.1 一台LVS挂了怎么办
单台LVS再快也有单点风险。机器硬件故障、内核panic、网卡松动,任何一个都可能导致入口不可用。所以生产上至少部署两台LVS,一台Master,一台Backup,通过Keepalived的VRRP协议维护一个虚拟IP。正常情况下VIP在Master上,如果Master挂了,Backup会在几秒内接管VIP,继续转发请求。
Keepalived的架构分为两大部分:VRRP负责做VIP的漂移,ipvs管理负责维护LVS规则和后端健康检查。如果某台Real Server挂了,Keepalived会把它从转发规则中摘除,恢复后再自动加入。整个过程对客户端透明,不需要改任何访问地址。
3.2 配置Keepalived的关键参数
Keepalived的配置文件是/etc/keepalived/keepalived.conf。一个典型的Master配置,global_defs里定义router_id用来标识节点;vrrp_instance段定义VRRP实例,interface要指定承载VIP的物理网卡,state设为MASTER,priority要高于Backup;virtual_ipaddress里写VIP。接着是virtual_server段,定义LVS虚拟服务,lb_algo为调度算法,lb_kind为DR模式,protocol为TCP。
Backup节点的区别主要是state设为BACKUP,priority调低,其他地方保持一致。这里有个容易踩的坑:authentication里的auth_pass,主备必须一致,否则VRRP协商不通过;virtual_router_id也要一致,且在同一网段内不要和别的VRRP实例冲突。
3.3 健康检查选哪种好
Keepalived的virtual_server段里,real_server节点下要配置健康检查。常见的有TCP_CHECK和HTTP_GET。TCP_CHECK就是尝试建立TCP连接,连接成功就认为服务正常,适合四层负载的场景。HTTP_GET会发一个HTTP请求检查返回状态码,可以配置url和status_code,适合需要确认业务层是否可用的服务。
如果后端是Nginx或Spring Boot,我一般优先用TCP_CHECK,因为开销小,稳定性高。HTTP_GET虽然能探测到应用层错误,但每次探测都要建立HTTP连接,稍微费一点资源。生产环境里判断一台Nginx是否可用,TCP端口通不通已经说明很多问题;如果你的服务有特殊的健康检查URL,比如访问/healthz返回200,那就用HTTP_GET。不要为了追求检查深度而让Keepalived频繁发起请求,检查超时时间connect_timeout建议设置在3到5秒,重试次数3次,避免误判。
4. 从零搭建一套可用的LVS DR集群
4.1 规划网络拓扑和机器
假设我们要搭建一套对外提供HTTP服务的集群,整体结构是两台LVS(一主一备),两台Real Server运行Nginx,VIP为192.168.10.100,DIP分别为192.168.10.11和192.168.10.12,RIP分别为192.168.10.21和192.168.10.22。所有机器在同一个C类网段192.168.10.0/24,网关都指向路由器。
很多教程会把两台LVS的eth0配成192.168.10.11和192.168.10.12,再给VIP加上去。我在生产中的习惯是把VIP作为独立地址配到eth0上,物理机本身的IP保持不变,这样Keepalived在切换时不会干扰原有SSH连接。Real Server上,我们把VIP配置在lo接口的别名lo:0上,掩码必须是32位,否则RS有可能响应客户端的ARP请求,把流量引到后端去。
4.2 Director端LVS配置
如果是裸装LVS,需要先安装ipvsadm。在CentOS/RHEL系统上执行yum install -y ipvsadm,Debian/Ubuntu用apt install -y ipvsadm。然后加载内核模块modprobe ip_vs,开机自启可以写到/etc/modules-load.d/ip_vs.conf里。
手动添加一条转发规则,VIP是192.168.10.100,端口80,算法wrr,模式DR,后端有两台。执行ipvsadm -A -t 192.168.10.100:80 -s wrr,然后add -t -r 192.168.10.21:80 -g -w 1,第二台权重给2。最后用ipvsadm -L -n查看规则。这套手动命令主要用来验证功能,真正生产还是靠Keepalived维护规则,因为机器重启后命令会丢,Keepalived启动时会自动把配置加载进内核。
4.3 Real Server端的VIP和ARP抑制
每台Real Server需要做三件事:第一,安装Nginx并监听80端口,这个不多说;第二,把VIP绑定到lo:0;第三,配置ARP抑制。
绑定VIP的命令为ip addr add 192.168.10.100/32 dev lo label lo:0,如果你用ifcfg文件管理网络,可以新增/etc/sysconfig/network-scripts/ifcfg-lo:0,写入DEVICE=lo:0、IPADDR=192.168.10.100、NETMASK=255.255.255.255、ONBOOT=yes,然后重启网络服务。注意NETMASK必须写32位,这是DR模式的一个重要细节。
最关键的ARP抑制填写到/etc/sysctl.conf,配置net.ipv4.conf.all.arp_ignore=1、net.ipv4.conf.all.arp_announce=2,同时lo接口和默认接口也要分别设置。完整的配置我建议这样写:
code复制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
然后执行sysctl -p。如果不想重启任何服务,也可以直接运行下面的命令临时生效。我建议写进文件,因为重启后这些配置还必须存在。
4.4 Keepalived高可用配置样例
主节点/etc/keepalived/keepalived.conf大概长这样:
code复制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 1111
}
virtual_ipaddress {
192.168.10.100/24 dev eth0
}
}
virtual_server 192.168.10.100 80 {
delay_loop 6
lb_algo wrr
lb_kind DR
protocol TCP
real_server 192.168.10.21 80 {
weight 1
TCP_CHECK {
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
real_server 192.168.10.22 80 {
weight 2
TCP_CHECK {
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
}
备节点的配置只需要把state改为BACKUP,priority改为90,router_id改成LVS_BACKUP,其余内容保持一致。这里有个细节:virtual_ipaddress里的掩码,写成192.168.10.100/24和写成192.168.10.100/32效果不同。如果VIP所在的物理网段就是/24,建议按/24配置,这样作为网关的设备能正确学到VIP的ARP记录。但有的时候你不想让VIP占用广播域,也可以写/32并配合静态路由,生产上我更推荐保持和物理网段一致。
配置完后执行systemctl start keepalived,在主节点上执行ip addr show eth0,能看到VIP已经加上。再执行ipvsadm -L -n,能看到两条Real Server的转发规则,状态为Active。
4.5 验证集群转发是否正常
在客户端机器上连续访问http://192.168.10.100/,多次刷新看返回内容,正常的话每次请求会落在不同的Real Server上。如果你给两台Nginx写了不同的首页标识,就能看到轮询效果。然后手动停掉其中一台Nginx,Keepalived的TCP_CHECK会在3秒内发现异常,把这条Real Server标记为不可用,后续请求全部落到另一台。把这个挂掉的Nginx再启动,几秒后规则会自动恢复。
主备切换验证也一样,直接在Master上执行systemctl stop keepalived,等待几秒,VIP会漂移到Backup上。在Backup上执行ip addr show eth0能看到VIP,然后整体服务依然正常。这就是一个可以交付给业务方使用的LVS高可用集群。
5. 常见报错和排障经验
5.1 配置时报错 different numbers of ports
很多同学在给LVS添加虚拟服务和Real Server时遇到一个问题,报错提示different numbers of ports,然后规则加不上去。这个报错的核心意思是,虚拟服务和后端Real Server的端口数量不一致。比如你添加虚拟服务时用ipvsadm -A -t 192.168.10.100:80,端口是80;给Real Server添加规则时却写成ipvsadm -a -t 192.168.10.100:80 -r 192.168.10.21:8080 -g,虚拟服务是80端口,Real Server是8080端口,两边端口数量对不上,内核就拒绝添加。
解决办法很简单,要么把Real Server也改成80端口,要么使用端口映射能力时保持端口数一致,比如都用8080,或者干脆不用LVS做不同端口映射,因为DR模式下LVS根本不修改端口,Real Server的监听端口必须和VIP服务端口一致。如果你真的需要把80端口的流量转发给后端8080,需要选择NAT模式,DR模式做不到。我遇到不少新手踩这个坑,所以在这里特别提醒一下。
5.2 页面时通时不通,多半是ARP抑制没做干净
集群搭建完成后,有时候发现客户端访问VIP一会儿成功一会儿失败,用arp-scan扫描局域网还会发现VIP对应的MAC地址在Director和Real Server之间反复横跳。这通常就是Real Server的ARP抑制没有生效。除了检查sysctl.conf,还要看是不是所有接口都配置了。有些教程只配置了all和lo,但实际网卡eth0上依然会响应ARP。所以我建议配置时显式加上eth0的配置,并且把整个网络的配置都统一覆盖。
另外要注意,如果你在Real Server上给lo绑了VIP,却忘了配置ARP抑制,那问题更隐蔽。客户端发往VIP的ARP请求,Real Server会响应,把VIP的MAC地址回成自己,一旦Director收到流量就断了。这种问题排查时用tcpdump抓ARP包最直观,在客户端或交换机上抓包,看回应ARP的MAC地址是哪台机器,基本就能定位。
5.3 高并发下丢包或SYN队列溢出
LVS转发性能虽然好,但高并发场景下也可能出现SYN队列被塞满的情况。现象是客户端TCP连接超时,线上偶发连接重置。可以在Director上执行netstat -s | grep -i "SYN"查看SYN丢弃数,如果SYN to LISTEN sockets dropped特别多,可以适当调大参数:
code复制net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
同时系统层面还要注意nf_conntrack的容量。LVS使用netfilter框架,要维护连接跟踪表。如果连接数太大,conntrack表满了会出现nf_conntrack: table full的报错。调大nf_conntrack_max并缩短超时时间,是常见的优化手段。不过在生产机上,你可以参考一下当前值,再结合业务连接数来调整,不要盲目调太大导致内存占用过高。
5.4 后端服务权重和会话保持的取舍
wrr算法下,权重分配并不完全是精确的。如果你权重设为1和2,LVS会尽量按比例分配,但短连接场景下会有一些波动。对于需要严格会话保持的场景,LVS的sh算法是基于源IP哈希的,如果客户端出口IP变化,会话就会丢失。所以在业务设计上,不要把状态存在本地,而是依赖Redis或数据库。真实场景里我用LVS承接的往往是RESTful API或静态资源,这类接口自带无状态属性,非常适合做水平扩展。
6. 运维层面的几个提醒
6.1 不要在LVS上做过多业务逻辑
LVS最大的价值是高性能转发,但它不对内容做解析。有人想把URL重写、缓存这类逻辑塞给LVS,这完全是错误方向。LVS前端只需要维护VIP和Real Server的健康状态,业务特性都放到Nginx层去实现。这样才能让每台机器的职责更清晰,出问题也好排查。
6.2 监控别忘了看ipvs和vrrp状态
用Prometheus监控LVS是个好习惯。你至少要看Director的ip_vs连接数、后端Real Server的活跃连接数、非活跃连接数,以及VRRP状态是否稳定。ipvsadm -L -n --stats可以查看实时连接和字节数,提前发现流量倾斜的苗头。如果发现某一台Real Server连接数持续偏高,去看这台机器的负载、网络和磁盘IO,很多问题会先出在这里。
6.3 同步配置时注意文件版本
Keepalived配置文件要加入版本管理,主备节点的配置除了state和priority以外,最好保持一致。我见过一次故障,因为主备的real_server列表先后更新,导致备机接管后转发规则里少了一台后端的服务,业务损失惨重。建议用Ansible或脚本同步配置,变更前先diff,变更后立即检查主备状态。
我个人在运维这套集群两年多的时间里,最大的体会是:LVS并不复杂,但它很底层,一旦出问题,影响面会非常大。学习LVS不只是学会几条命令,更重要的是把网络模型、ARP协议、内核参数这些基础知识补扎实。只有把原理吃透了,遇到问题才不会被表象迷惑。最后再分享一个排查技巧:如果你实在怀疑LVS转发异常,可以在Director上用tcpdump抓eth0,过滤host VIP,看SYN包是否进来,出去的目标MAC是不是后端机器的网卡MAC。这套抓包法帮我定位过不少隐蔽问题,希望你们也能用上。
