1. 企业级流量入口的架构选型思考
1.1 为什么负载均衡是绕不开的第一道坎
做后端架构这些年,我越来越觉得负载均衡是整个系统里最容易被低估、却又最不能出错的一环。业务代码写崩了,大不了回滚一个版本;数据库慢查询了,加个索引还能救;但负载均衡这一层要是出了问题,那就是整个服务入口直接瘫痪,所有下游节点全部失联,用户端看到的就是白屏或者502。这种故障的杀伤力,跟单点服务挂掉完全不是一个量级。
LVS(Linux Virtual Server)在国内互联网公司的普及率非常高,尤其是那些日活千万级以上的业务,几乎都能看到它的身影。原因很直接:它是基于内核态的四层转发,性能损耗极低,单台机器扛几十万并发连接是常规操作。相比Nginx这种七层代理,LVS不解析应用层协议,只做IP和端口的转发决策,所以它的吞吐能力要高出好几个数量级。当然代价就是功能相对"朴素",不能做URL路由、不能改Header、不能做SSL卸载,这些活儿得交给后端的七层代理或者应用本身去处理。
这篇文章我想聊的是,怎么把LVS真正用起来,不是那种"装个ipvsadm敲两条命令就完事"的玩具级演示,而是企业环境里需要考虑高可用、会话保持、健康检查、内核调优、故障切换的完整方案。适合已经有一定Linux基础、正在搭建或者优化自己服务入口的运维和后端同学。如果你还在用单台Nginx扛所有流量,或者对"四层"和"七层"的区别只有模糊概念,那这篇内容应该能帮你把思路理清楚。
1.2 四层和七层到底差在哪里
很多人一开始会纠结:既然Nginx也能做负载均衡,为什么还要多引入一层LVS?这个问题我在不同团队里被问过不下十次。核心答案就一个字:性能。但这个"性能"背后涉及的东西值得展开说。
四层负载均衡工作在传输层,它只看数据包的源IP、目标IP、源端口、目标端口,然后根据调度算法决定把这个包转发给哪台后端服务器。整个过程在内核空间完成,不需要把数据从内核态拷贝到用户态,也不需要建立完整的TCP连接再转发。你可以把它想象成一个非常聪明的邮局分拣员,只看信封上的地址就把信扔到对应的邮筒里,根本不拆开看里面写了什么。
七层负载均衡则工作在应用层,它需要先和客户端建立完整的TCP连接,然后解析HTTP请求,根据URL、Header、Cookie等信息做转发决策。这个过程涉及大量的用户态和内核态之间的数据拷贝,还要维护连接状态,所以单机性能天花板要低得多。但它的优势在于灵活,能做基于路径的路由、能做灰度发布、能改写请求内容。
企业里的常见做法是两者配合:LVS在最前面做四层转发,把流量分给一组Nginx,Nginx再做七层处理转发给真正的应用服务器。这样LVS扛住了海量连接和DDoS攻击的第一波冲击,Nginx专注于业务层的灵活路由。这个架构在电商大促、直播活动这种流量尖峰场景下特别管用。
1.3 三种转发模式的取舍逻辑
LVS支持三种主要的转发模式:NAT、DR(Direct Routing)和TUN(IP Tunneling)。这三种模式的选择直接决定了你的网络拓扑和性能上限,必须在一开始就想清楚。
NAT模式最简单,但性能最差。它的原理是LVS修改请求包的目标IP为后端服务器IP,后端处理完再把响应包发回LVS,由LVS改回源IP返回给客户端。问题在于所有响应流量都要经过LVS,LVS成了瓶颈,而且后端服务器需要把网关指向LVS。这种模式适合小规模场景,比如后端只有几台机器、流量不大的内部系统。
DR模式是生产环境用得最多的。它的巧妙之处在于:LVS只修改请求包的MAC地址,把包转发给后端,后端处理完后直接把响应包发给客户端,不经过LVS。这样LVS只处理入站流量,出站流量由后端自己搞定,吞吐能力大幅提升。代价是LVS和后端必须在同一个二层网络里,而且后端服务器需要在回环接口上配置VIP,并且抑制ARP响应,否则会跟LVS抢VIP。
TUN模式适合跨机房场景,LVS把请求包封装在IP隧道里发给后端,后端解封装后处理,响应直接返回客户端。这种模式支持后端和LVS不在同一网段,但需要后端支持IP隧道协议,而且多了一层封装解封装的开销。
我个人的经验是:同机房内优先用DR,跨机房考虑TUN,NAT只在测试环境或者极简场景下用。下面这张表可以帮你快速对比:
| 对比维度 | NAT模式 | DR模式 | TUN模式 |
|---|---|---|---|
| 性能 | 低 | 高 | 中 |
| 网络要求 | 后端网关指向LVS | 同一二层网络 | 支持IP隧道 |
| 响应路径 | 经过LVS | 直接返回客户端 | 直接返回客户端 |
| 端口映射 | 支持 | 不支持 | 支持 |
| 适用场景 | 小规模测试 | 同机房生产 | 跨机房部署 |
2. 核心组件与调度算法深度拆解
2.1 ipvsadm与内核模块的配合机制
LVS的核心其实分两部分:内核里的IPVS模块和用户空间的ipvsadm工具。IPVS模块负责实际的数据包处理和转发决策,ipvsadm则是我们用来配置和管理转发规则的工具。理解这个分工很重要,因为很多"配置了不生效"的问题,根源就在于内核模块没加载或者版本不匹配。
在开始配置之前,第一件事是确认内核是否支持IPVS。大部分主流发行版的内核都默认编译了IPVS模块,但有些精简版或者定制内核可能没有。你可以用modprobe ipvs尝试加载,然后用lsmod | grep ip_vs确认。如果加载失败,可能需要重新编译内核或者安装对应的内核模块包。
ipvsadm的配置逻辑围绕三个概念展开:虚拟服务(Virtual Service)、真实服务器(Real Server)和调度算法。虚拟服务就是你对外暴露的VIP和端口,真实服务器就是后端实际处理请求的节点。一条典型的配置命令长这样:
ipvsadm -A -t 192.168.1.100:80 -s wrr ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.101:80 -g -w 100 ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.102:80 -g -w 100第一行是添加虚拟服务,-t指定TCP协议和VIP端口,-s wrr指定加权轮询算法。后面两行是添加真实服务器,-g表示DR模式,-w是权重。这些命令看起来简单,但每一条背后都有讲究,比如权重怎么定、算法怎么选,后面会详细说。
2.2 调度算法的选择不是拍脑袋
LVS提供了十几种调度算法,但实际生产环境常用的就那么几种。选错了算法,轻则负载不均,重则某些后端被打爆而其他后端闲着。我把常用的几种列出来,结合场景说说怎么选。
轮询(RR)是最简单的,请求依次分给每台后端。适合后端配置完全一致、处理能力相同的场景。但现实中很少有完全一致的机器,所以纯轮询用得不多。
加权轮询(WRR)在轮询基础上加了权重,权重高的分到的请求多。这个权重需要根据后端的CPU、内存、带宽来定。我一般会先给一个初始权重,然后观察一段时间的实际负载再调整。比如两台机器配置是8核16G和4核8G,权重可以设成2:1,但实际可能设成3:1更合适,因为性能不是线性关系。
最少连接(LC)把新请求分给当前连接数最少的后端。这个算法在请求处理时间差异大的场景下表现很好,因为它能动态感知后端的忙闲程度。但有个坑:如果某台后端因为慢查询导致连接堆积,LC会暂时不再给它新请求,等它慢慢消化,这其实是好事。但如果所有后端都堆积了,LC也救不了。
加权最少连接(WLC)是LC的加权版本,也是我实际用得最多的算法。它在连接数的基础上除以权重,相当于认为权重高的机器能承受更多连接。公式是(active_conns * 256 + inactive_conns) / weight,值最小的后端获得新请求。这个算法在异构集群里表现很稳。
源地址哈希(SH)根据客户端IP做哈希,同一个IP的请求总是落到同一台后端。这个算法主要用于会话保持,但有个明显缺陷:如果大量客户端经过同一个NAT网关出来,它们的源IP相同,就会全部落到同一台后端,导致负载严重倾斜。所以SH只适合客户端IP分散的场景。
目标地址哈希(DH)根据目标IP做哈希,主要用于后端是缓存服务器的场景,保证同一份缓存数据总是落在同一台机器上。
| 算法 | 适用场景 | 注意事项 |
|---|---|---|
| RR | 后端配置完全一致 | 实际很少用 |
| WRR | 后端配置有差异 | 权重需动态调整 |
| LC | 请求处理时间差异大 | 动态感知忙闲 |
| WLC | 异构集群通用 | 生产首选 |
| SH | 需要会话保持 | NAT场景慎用 |
| DH | 缓存服务器集群 | 保证缓存命中 |
2.3 会话保持的几种实现路径
会话保持是负载均衡里绕不开的话题。用户登录后,后续请求必须落到同一台后端,否则session丢了就得重新登录。LVS本身是四层转发,它不理解HTTP协议,所以不能像Nginx那样基于Cookie做会话保持。但LVS提供了几种替代方案。
第一种是前面提到的源地址哈希(SH)算法。配置简单,但NAT场景下会出问题。而且如果后端某台机器挂了,那部分用户的session就丢了,需要重新登录。
第二种是会话共享,把session存到Redis或者数据库里,后端无状态。这是最彻底的方案,也是我推荐的做法。虽然引入了一个新的依赖,但它解决了会话保持的根本问题,后端可以随意扩缩容,不用担心session丢失。
第三种是在LVS后面加一层Nginx,由Nginx基于Cookie做会话保持。这样LVS只管四层转发,Nginx处理七层的会话逻辑。这个方案兼顾了性能和灵活性,但架构复杂度增加了。
第四种是持久连接模板,LVS可以配置在一定时间内,同一源IP的请求都转发到同一台后端。这个跟SH类似,但可以设置超时时间。配置命令是ipvsadm -p 600,表示600秒内的同一源IP请求都走同一后端。
提示:会话保持方案的选择取决于你的业务对session丢失的容忍度。金融类业务通常要求session绝对不丢,那就必须用共享存储方案;普通资讯类业务,偶尔重新登录用户也能接受,用SH或者持久连接模板就够了。
3. 高可用架构与实操部署全流程
3.1 Keepalived+LVS的双机热备方案
单台LVS本身就是单点,它挂了整个服务就没了。所以生产环境必须做高可用,最常见的方案是Keepalived+LVS双机热备。Keepalived通过VRRP协议实现VIP的漂移,主节点正常时持有VIP,主节点故障时备用节点接管VIP,整个过程对客户端透明。
这个方案的原理说起来简单,但配置细节很多。首先是VRRP的优先级设置,主节点优先级要高于备用节点,比如主150备100。然后是抢占模式和非抢占模式的选择:抢占模式下,主节点恢复后会抢回VIP;非抢占模式下,主节点恢复后不抢,VIP继续留在备用节点。我一般用非抢占模式,避免VIP频繁漂移导致连接中断。
Keepalived的配置文件主要分三块:global_defs、vrrp_instance和virtual_server。global_defs里配置路由ID和通知脚本;vrrp_instance定义VRRP实例,包括状态、接口、优先级、认证方式;virtual_server定义LVS的转发规则,包括VIP、端口、调度算法和真实服务器列表。
一个容易踩的坑是防火墙。VRRP协议用的是组播地址224.0.0.18,如果防火墙没放行,主备之间无法通信,会导致双主(两台都认为自己是主)的严重问题。所以配置之前一定要确认防火墙规则,或者直接关掉防火墙(内网环境)。
另一个坑是Keepalived的健康检查。默认的健康检查是TCP连接检查,但有些服务TCP端口开着但应用已经假死了,这时候需要更精细的检查,比如HTTP状态码检查或者自定义脚本检查。Keepalived支持配置MISC_CHECK来执行自定义脚本,脚本返回0表示健康,返回非0表示故障。
3.2 DR模式后端服务器的ARP抑制配置
DR模式有一个必须处理的细节:后端服务器需要在回环接口上配置VIP,但同时又不能让外界感知到这个VIP,否则会跟LVS抢流量。解决办法是配置ARP抑制。
具体来说,需要修改两个内核参数:
# 忽略来自其他接口的ARP请求 echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore # 使用最合适的源地址响应ARP请求 echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce然后在回环接口上配置VIP:
ifconfig lo:0 192.168.1.100 netmask 255.255.255.255 up这里有个细节:netmask必须设成255.255.255.255,不能设成255.255.255.0。因为如果设成后者,后端会认为VIP所在的网段是直连的,可能会发出ARP请求,导致网络混乱。
这些配置需要持久化,否则重启后就失效了。可以把它们写到/etc/sysctl.conf和网络配置脚本里。我见过不少团队在测试环境配好了,上线后重启服务器发现VIP没了,排查半天才发现是没做持久化。
3.3 完整部署流程与验证方法
下面我把DR模式+LVS+Keepalived的完整部署流程梳理一遍。假设你有两台LVS节点(主备)和两台后端服务器,VIP是192.168.1.100。
第一步:LVS节点安装软件
yum install -y ipvsadm keepalived第二步:配置Keepalived
主节点配置文件/etc/keepalived/keepalived.conf:
global_defs { router_id LVS_MASTER } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 150 advert_int 1 authentication { auth_type PASS auth_pass 1234 } virtual_ipaddress { 192.168.1.100 } } virtual_server 192.168.1.100 80 { delay_loop 6 lb_algo wlc lb_kind DR persistence_timeout 300 protocol TCP real_server 192.168.1.101 80 { weight 100 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } real_server 192.168.1.102 80 { weight 100 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } }备用节点的配置类似,只需要改state为BACKUP,priority改为100,router_id改为LVS_BACKUP。
第三步:后端服务器配置
# 配置ARP抑制 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 # 配置VIP到回环接口 ifconfig lo:0 192.168.1.100 netmask 255.255.255.255 up第四步:验证
在主节点上执行ipvsadm -L -n,应该能看到虚拟服务和真实服务器的列表。然后用curl访问VIP,观察请求是否被分发到不同的后端。可以临时停掉一台后端,看LVS是否自动把它剔除。
注意:测试的时候一定要用不同的客户端IP去访问,否则因为持久连接的原因,所有请求都会落到同一台后端,你会误以为负载均衡没生效。
4. 生产环境常见故障与排查实录
4.1 后端服务器全部下线但服务正常
这个问题听起来矛盾,但确实发生过。现象是ipvsadm -L显示所有真实服务器都是inactive状态,但用户访问却正常。排查后发现,是健康检查的TCP_CHECK配置有问题,检查端口写错了,导致LVS认为后端都挂了,但实际上后端服务是正常的。
还有一种可能是健康检查频率太高,后端服务器因为并发连接数限制拒绝了检查请求,LVS误判为故障。解决办法是调整delay_loop和connect_timeout参数,给后端足够的响应时间。
4.2 VIP漂移后旧连接不释放
Keepalived做VIP漂移时,已经建立的连接不会立即断开,而是会继续走原来的路径。如果主节点已经挂了,这些连接就会超时。对于长连接业务(比如WebSocket),这个问题尤其明显。
解决办法是在Keepalived的notify脚本里,在节点变成BACKUP时主动清理IPVS的连接表:
#!/bin/bash if [ $3 == "BACKUP" ]; then ipvsadm -C fi这个脚本在节点状态变为BACKUP时执行,清空所有转发规则和连接表,强制客户端重新建立连接。
4.3 调度不均导致部分后端过载
WRR和WLC算法都依赖权重配置,如果权重设置不合理,就会出现部分后端过载。我遇到过一个案例:两台后端配置相同,权重也相同,但其中一台的负载明显高于另一台。排查后发现是持久连接导致的,因为测试时用的同一个客户端IP,所有请求都落到了同一台。
生产环境里,如果发现调度不均,首先要确认是不是持久连接或者SH算法导致的。如果是,考虑改用WLC算法并关闭持久连接。如果确实是权重问题,根据实际负载调整权重。我一般会观察CPU使用率和连接数两个指标,CPU高的降权重,连接数多的降权重。
4.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| VIP无法访问 | Keepalived未启动 | 检查进程和日志 | 启动Keepalived |
| 后端全部inactive | 健康检查配置错误 | 检查端口和超时 | 修正检查配置 |
| 负载不均 | 持久连接或SH算法 | 查看连接分布 | 改用WLC算法 |
| 双主现象 | VRRP通信失败 | 检查防火墙和组播 | 放行VRRP协议 |
| 响应慢 | 后端性能瓶颈 | 查看后端负载 | 调整权重或扩容 |
| VIP漂移后连接超时 | 旧连接未清理 | 检查notify脚本 | 添加清理逻辑 |
4.5 内核参数调优的几个关键点
LVS的性能很大程度上取决于内核参数。默认参数适合通用场景,但高并发下需要调整。以下几个参数我每次部署都会检查:
net.ipv4.ip_forward必须设为1,否则LVS无法转发数据包。这个是最基础的,但有时候系统默认是0,忘了改就会导致转发失败。
net.ipv4.vs.conn_reuse_mode控制连接复用模式。默认是1,表示允许复用TIME_WAIT状态的连接。在高并发短连接场景下,这个参数能显著提升性能。但如果遇到端口耗尽的问题,可以尝试设为0。
net.ipv4.vs.expire_nodest_conn设为1时,如果后端服务器不可达,LVS会立即把连接转发到其他后端,而不是等待超时。这个参数能提升故障切换的速度。
net.ipv4.vs.schedule_icmp_error控制是否响应ICMP错误。一般设为0,避免ICMP错误影响转发决策。
这些参数可以通过sysctl -w临时修改,也可以写到/etc/sysctl.conf持久化。修改后需要重启IPVS或者重新加载配置才能生效。
5. 性能压测与容量规划经验
5.1 压测工具的选择与使用
LVS的性能压测跟普通服务不太一样,因为它本身不处理业务逻辑,所以压测的重点是转发能力和连接建立速度。常用的工具有ab、wrk、hey,但我更推荐用wrk,因为它支持多线程和长连接,能更好地模拟真实场景。
压测的时候要注意几点:一是压测客户端不能跟LVS在同一台机器上,否则会互相影响;二是要监控LVS的CPU和内存使用率,找到瓶颈点;三是要观察后端的负载是否均衡,如果后端先扛不住了,那瓶颈就不在LVS。
我一般会做几轮压测:第一轮用短连接,测连接建立速度;第二轮用长连接,测吞吐量;第三轮模拟故障,测切换速度。每轮压测都要记录关键指标,形成基线数据,方便后续对比。
5.2 容量估算的实用方法
LVS的容量估算主要看两个指标:并发连接数和每秒新建连接数。并发连接数取决于内存,每个连接大约占用几百字节的内核内存。每秒新建连接数取决于CPU,单核大约能处理几万到十几万的新建连接。
假设你的业务峰值是每秒10万请求,平均每个请求处理时间100毫秒,那么并发连接数大约是1万。这个量级单台LVS轻松搞定。但如果每秒新建连接数是10万,那就需要多核CPU,因为新建连接是CPU密集型操作。
我的经验是:单台4核8G的LVS,用DR模式,大约能扛住50万并发连接和每秒20万新建连接。当然这个数字跟具体的业务场景和内核参数有关,建议在实际环境中压测验证。
5.3 扩容与缩容的操作要点
扩容后端服务器时,直接在后端配置好服务,然后在LVS上执行ipvsadm -a添加即可。但要注意,新加的后端需要先预热,不要一上来就给满权重。我一般先给10%的权重,观察几分钟,确认没问题再逐步加到正常权重。
缩容时,先把要下线的后端权重设为0,等现有连接处理完,再执行ipvsadm -d删除。直接删除会导致正在处理的请求中断,用户体验很差。
LVS节点本身的扩容比较麻烦,因为涉及到VIP的漂移和VRRP的重新选举。一般不建议频繁扩缩LVS节点,而是通过增加后端来提升整体容量。如果LVS确实成了瓶颈,可以考虑用DNS轮询做多组LVS的负载均衡。
6. 个人实操体会与后续扩展方向
6.1 踩过几次坑之后的几点体会
第一,配置文件一定要版本管理。Keepalived的配置文件改错了,可能导致VIP漂移或者转发规则丢失。我现在的做法是把配置文件放到Git里,每次修改都提交,出问题了直接回滚。
第二,监控比配置更重要。LVS本身不产生日志,出问题了很难排查。所以一定要配置监控,监控VIP的可达性、后端服务器的健康状态、连接数的变化趋势。我一般会用脚本定期执行ipvsadm -L -n并把结果推送到监控系统。
第三,测试环境要尽量模拟生产。我见过太多团队在测试环境用NAT模式,生产环境用DR模式,结果上线后各种问题。测试环境应该跟生产环境保持一致的架构和配置,哪怕规模小一点。
第四,不要忽视文档。LVS的配置涉及网络、内核、应用多个层面,时间长了很容易忘记当初为什么这么配。我习惯在配置文件里写注释,说明每个参数的含义和调整原因,方便自己和同事后续维护。
6.2 这个架构还能怎么扩展
LVS+Keepalived的方案已经能满足大部分场景,但如果业务继续增长,可以考虑几个扩展方向。一是引入ECMP(等价多路径路由),用多台LVS同时持有同一个VIP,通过路由器的ECMP能力做负载均衡,这样LVS层也能水平扩展。二是结合DPDK或者XDP技术,把转发性能再提升一个数量级,不过这个对技术栈要求比较高。三是把LVS容器化,用Kubernetes的Service或者Ingress来管理,不过容器网络跟LVS的配合还有一些细节需要处理。
最后分享一个小技巧:如果你不确定调度算法选哪个,先用WLC跑一段时间,观察后端的负载分布。如果发现某台后端明显偏忙,再考虑调整权重或者换算法。不要一开始就追求完美配置,实际运行数据比理论计算靠谱得多。