搞负载均衡这一挂的朋友,应该都绕不开LVS(Linux Virtual Server)这个名字。尤其是在早年互联网架构里,LVS几乎是四层负载均衡的代名词。现在很多团队一上来就扔一台Nginx在前面扛流量,但真正到了大规模、高并发、追求极致性能的场景,内核态的LVS依然是很多架构师心里最稳的那张底牌。
这篇文章我不打算念文档,而是结合我自己从搭实验环境到压测、再到生产环境排障的真实经历,把LVS的核心设计、三种工作模式、调度算法选型、keepalived高可用组合、性能调优方向和常见坑位一次讲透。无论你是刚接触负载均衡的运维新人,还是已经在用Nginx但想搞明白LVS为什么更快的老手,这篇文章都值得花几分钟读进去。
1. LVS到底是什么东西,为什么它能这么硬
先说个可能很多人都没仔细想过的事实:LVS是1998年由章文嵩博士发起的开源项目,到现在二十多年了,它依然是Linux内核里自带的一套负载均衡解决方案,核心模块叫ipvs,用户态的管理工具叫ipvsadm。2017年ipvs正式合并进Linux内核主线,也就是说,不管你用的是什么发行版,只要内核够新,LVS的能力其实一直躺在系统里。
它和Nginx、HAProxy最大的区别在哪?LVS跑在内核态,工作在TCP/IP协议栈的IP层,属于四层负载均衡。这意味着数据包从网卡进来之后,直接在内核里就被改写和转发,根本不需要把数据拷贝到用户态,也不需要建立TCP连接来做代理转发。Nginx处理每一个请求,都要经过“内核收包 -> 用户态解析HTTP -> 内核发包”这样反复的上下文切换和数据拷贝,而LVS直接把这一段路给省了。用一句直白的话说:LVS是在网络栈的“高速公路”上做立交桥,Nginx是在收费站里做路线引导,前者天然就快一个量级。
实际项目里,LVS最常见的用法是放在整个流量入口的最前端,比如架构是LVS -> Nginx -> 应用服务。LVS用极低的资源消耗扛住海量TCP连接请求,再把流量分发给后端的Nginx层去做HTTP层面的处理。我在压测环境里做过对比,同样是一台8核16G的机器,Nginx单机扛个几万QPS就要开始紧张CPU,而LVS跑在纯转发模式下,几十万PPS的连接处理是很轻松的事情,资源的消耗几乎可以忽略不计。
不过说句公道话,LVS不是万能的。它是四层设备,看不到HTTP的URL、Header、Cookie这些七层信息,没法做路径级别的路由,也没法做内容缓存。所以它适合负责流量分发和接入层高可用,而不是替代Nginx那层业务逻辑。理解了这个定位,后面所有的架构选择和调优思路就都顺了。
2. 三种工作模式拆解:NAT、DR、Tunnel到底怎么选
LVS支持三种工作模式,很多初学者就是在这里被绕晕的。我在带团队的时候经常打一个比方:LVS就像个前台快递分发中心,NAT模式是每一份快递都要寄回分发中心再发出去,DR模式是分发中心只管把快递塞给快递员,快递员直接送到客户手里,Tunnel模式则是用特快专递通道把包裹整个空投到其他城市的分站点。三种模式各有各的适用场景,选错了,要么性能上不去,要么网络结构搞不定。
2.1 NAT模式:最简单,但容易成为瓶颈
NAT模式下,客户端请求到达LVS后,LVS通过改写数据包的目的IP地址,把流量转发给后端的Real Server(简称RS)。RS处理完请求后,回包要再送回给LVS,由LVS改写源IP地址,伪装成VIP再返回给客户端。
这个模式的优点是非常直观,后端RS只需要配置一个私网IP,不需要做任何额外设置,网关直接指向LVS的内网IP就行。但它的瓶颈也很明显:所有的入向流量和出向流量都要经过LVS,LVS既要改请求包的目的IP,又要改回包的源IP,压力非常大。一旦业务流量大了,LVS节点最先崩的不是CPU,而是网卡带宽。所以我通常只在集群规模小、流量可控的场景下推荐NAT模式,比如几十台以内的Web集群,或者对LVS不熟悉、想快速验证负载均衡效果的实验环境。
配置NAT模式前记得确认一件事:把LVS的IP转发功能打开,也就是设置net.ipv4.ip_forward = 1,不然数据包到了LVS手里也转发不出去。
2.2 DR模式:生产环境用得最多的方案
DR模式全称是Direct Routing,相比之下它巧妙得多。请求到达LVS后,LVS不改IP,只把数据链路层的MAC地址改为目标RS的MAC地址,然后在同一个二层网络内把包转发出去。RS接收到请求后,发现自己的一块虚拟网卡上也绑定了VIP(就是写在lo接口上的那个VIP地址),于是直接处理请求,并且以VIP为源IP直接把响应包返回给客户端网关。
这里最妙的一点是:响应流量完全不用再绕回LVS。客户端发一个请求给LVS,LVS只负责“分发包裹”,RS拿到包裹后直接“送货上门”。所以DR模式下LVS节点的压力只有入向流量的一半,吞吐能力比NAT模式高出好几倍,这也是为什么绝大多数线上架构都采用DR模式的原因。
DR模式有个硬性前提:LVS和所有RS必须在同一个二层广播域里,也就是同一台交换机下,不能跨路由转发,否则MAC改写就没法到达目标机器了。另外RS上必须做ARP抑制,否则RS上的VIP一旦响应ARP广播,客户端就会发现VIP对应多个MAC地址,直接绕过LVS把请求发到某台RS上,负载均衡就全乱套了。这部分我在后面的实操章节会详细展开。
2.3 Tunnel模式:跨机房分发的最佳解
Tunnel模式全称是IP Tunneling,原理有点像DR模式的远程版。LVS把原始数据包用IPIP隧道封装成新的IP包,通过互联网或者专线隧道发送给位于不同物理位置的RS。RS收到后解封装,还原出原始请求包并处理,响应包同样直接返回给客户端。
这种模式解决的核心问题是跨机房、跨地域的流量调度。比如你在北京部署了LVS入口,后端的RS分别在华北、华南两个机房,Tunnel模式就能把流量调度过去。缺点就是需要RS支持并开启IPIP隧道协议(一般要在RS上建立tunl虚拟隧道接口),运维复杂度比DR模式高不少。
三种模式的选型思路我整理成了一张表,方便对照:
| 模式 | 请求转发方式 | 响应流量路径 | RS要求 | 网络要求 | 适用场景 |
|---|---|---|---|---|---|
| NAT | 修改目的IP | 必须经LVS返回 | 无需额外配置 | 支持跨网段 | 小规模集群、快速验证 |
| DR | 修改MAC地址 | RS直连返回客户端 | 绑定VIP+ARP抑制 | 必须在同一二层网络 | 大多数生产环境 |
| Tunnel | 封装成隧道包 | RS直连返回客户端 | 开启IPIP隧道接口 | 可跨机房/跨网段 | 多机房异地调度 |
3. 调度算法:轮询之外的那些门道
LVS自带了十种调度算法,很多人配置的时候就选默认的wlc然后就不管了,其实算法的选择直接决定了流量分配的均衡程度和后端资源的利用率。我先把这些算法按固定和动态分成两大类,再聊聊实际选型中的一些体会。
固定调度算法不关心后端RS当前的负载情况,只管按逻辑分配:rr(轮询)、wrr(加权轮询)、dh(目标地址哈希)、sh(源地址哈希)。动态调度算法则会根据后端RS当前的活跃连接数等指标来动态判断该把请求交给谁:lc(最少连接)、wlc(加权最少连接)、sed(最短期望延迟)、nq(永不排队)、lblc(基于局部性的最少连接)、lblcr(带复制的基于局部性的最少连接)。
默认的wlc算法,它的实际计算公式是(active_conn + 1) / weight,谁的比值小就把请求给谁。这个算法最大的优点是在集群内RS性能有差异的时候能自动倾斜,性能好的机器配上更大的权重就会承担更多流量,性能弱的机器也不至于被打爆。我在生产环境用wlc配不同权重的经验是:2C4G的机器权重给2,8C16G的机器权重给5到6,效果比平均轮询好得多。
不过wlc不是万能的。如果后端所有RS的配置完全一致,用最简单的rr反而更省心,因为wlc需要维护实时的连接状态,调度器本身会多一点点开销。长连接场景下,比如WebSocket、数据库连接池这类,我反而建议用sh(源地址哈希),确保同一个客户端的请求总是被发往同一台RS,这样连接状态可以保证不漂移,业务逻辑上也不需要额外的状态同步。
至于lblc和lblcr,它们在处理缓存类业务时很好用,发往同一目标地址的请求会被尽量调度到同一台RS,这样缓存的命中率能提得很高。但这类算法的状态维护复杂,对调度器内存有一点压力,一般的业务用不上,碰到了再深入研究不迟。
4. 调度算法选型实战推荐
给一个最直接的参考表吧,这是我在不同业务场景下实际验证过的推荐:
| 业务场景 | 推荐算法 | 理由 |
|---|---|---|
| 普通HTTP短连接服务 | wlc | 默认算法,综合均衡效果好 |
| 后端机器配置差异大 | wrr或wlc | 用权重控制流量比例 |
| WebSocket等长连接 | sh | 保证同一会话固定调度到同一台RS |
| 数据库连接层 | sh | 避免连接跨实例漂移 |
| 缓存服务 | lblcr | 提升缓存命中率,减少回源 |
| 同配置简单集群 | rr | 开销最小,逻辑最透明 |
5. 实操:手把手搭一套LVS DR模式负载均衡
纸上谈兵没意思,这里我直接用一套最小化配置带你走一遍DR模式的完整落地流程。假设我用三台机器,前面是LVS调度器,后面两台RS提供Web服务。VIP我用192.168.10.100,两台RS的内网IP分别是192.168.10.11和192.168.10.12。
5.1 调度器配置
先用ipvsadm定义虚拟服务,注册后端真实服务器,并指定DR模式和权重:
ipvsadm -A -t 192.168.10.100:80 -s wlc ipvsadm -a -t 192.168.10.100:80 -r 192.168.10.11:80 -g -w 1 ipvsadm -a -t 192.168.10.100:80 -r 192.168.10.12:80 -g -w 1这里-A表示添加虚拟服务,-t指定协议类型为TCP,-s wlc指定调度算法;-a表示添加Real Server,-r指定RS地址,-g表示使用DR模式(gatewaying),-w设置权重。
LVS调度器自身的网卡或lo接口上还必须有VIP地址。一般建议绑在lo接口上避免影响外部通信:
ip addr add 192.168.10.100/32 dev lo同时打开路由转发(虽然DR模式不依赖三层转发,但保持标准设置更稳):
sysctl -w net.ipv4.ip_forward=15.2 后端RS配置
每台RS同样要在lo接口上绑定VIP,并设置严格的ARP抑制参数。为什么要抑制ARP?因为VIP如果通过ARP广播出去,交换机上就会同时出现多个VIP的MAC记录,客户端的ARP缓存表也会混乱,流量就不走LVS了。
ip addr add 192.168.10.100/32 dev lo sysctl -w net.ipv4.conf.all.arp_ignore=1 sysctl -w net.ipv4.conf.all.arp_announce=2 sysctl -w net.ipv4.conf.lo.arp_ignore=1 sysctl -w net.ipv4.conf.lo.arp_announce=2arp_ignore=1的意思是只回答目标IP是本接口地址的ARP请求,arp_announce=2的意思是ARP通告使用网络接口自身的最优本地地址,避免把VIP的MAC地址广播出去。这两个参数必须同时设置,而且要覆盖到lo接口和all接口,一个都不能少。生产环境里还要把这两个参数写进/etc/sysctl.conf,防止重启后失效。
5.3 验证与检查
配置完成后,你可以在调度器上查看当前的负载均衡列表:
ipvsadm -L -n --stats如果看到两台RS的ActiveConn和InActConn都在增长,说明转发已经正常。再用一台测试机反复请求VIP,观察请求是否随机落在两台RS上。
for i in $(seq 1 20); do curl -s http://192.168.10.100/ | grep hostname; done我自己在刚接触LVS的时候,最常犯的错就是RS上只把VIP绑上去,却忘了做ARP抑制,结果客户端直接绕过LVS打到了RS上,怎么查都查不出问题。这个细节建议新手反复确认。
6. keepalived组合:高可用版本的LVS才是真的能上线
单台LVS调度器无论性能多强,都只是单点。生产环境里LVS必须和keepalived组合使用,形成主备双机甚至多机。
keepalived的核心是VRRP协议,它通过组播报文在主备节点之间传递心跳信息。正常情况下主节点持有VIP并提供服务,备份节点在持续收不到主节点的心跳后会接管VIP,这样就实现了故障转移。架构上其实是一个虚拟路由组,对外始终只暴露那个VIP,客户端根本感觉不到后端调度器的变化。
一个精简的keepalived配置大致是下面这样:
global_defs { notification_email { admin@example.com } router_id LVS_DEVEL } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 123456 } virtual_ipaddress { 192.168.10.100 } } virtual_server 192.168.10.100 80 { delay_loop 6 lb_algo wlc lb_kind DR persistence_timeout 50 protocol TCP real_server 192.168.10.11 80 { weight 1 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } real_server 192.168.10.12 80 { weight 1 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } }两个节点配置里的state分别是MASTER和BACKUP,priority主高备低,advert_int是心跳间隔秒数,virtual_ipaddress就是对外提供的VIP。keepalived不仅负责VIP的漂移,还会周期性地用TCP_CHECK探测后端的RS健康状态,RS一旦宕机就自动摘除,恢复后自动加回。
我在配置了keepalived之后,最常遇到的一个问题是主备切换频繁触发。追查下来基本都是VRRP的心跳报文被防火墙拦了,或者主备节点的时间偏差太大。V2版本的VRRP用的是组播地址224.0.0.18,必须在防火墙放行这个组播地址,排查方向直接往这上面靠,基本都是这个原因。
还有一个小细节:persistence_timeout 50表示50秒的持久连接时间,这是为了让同一个用户在短时间内始终访问同一台RS,对需要保持会话的Web应用很友好。但如果后端是无状态服务,这个值建议设成0,否则流量会集中在某个RS上,等持久时间过了才重新均衡,浪费了后端资源。
7. 性能调优的几个关键参数,以及我踩过的坑
LVS本身的性能很高,但前提是你得把系统层面的参数配合上。这一节我挑几个真正影响吞吐量和稳定性的关键点来讲。
7.1 连接超时设置
LVS内核会维护一张连接状态表,不同协议的连接空闲多久后被回收,是用ipvsadm --set来控制的:
ipvsadm --set tcp tcpfin udp # 例如: ipvsadm --set 3600 120 300三个参数依次是TCP空闲超时时间、TCP FIN状态超时时间、UDP空闲超时时间。如果业务里有很多长连接,建议把TCP超时调高一些,比如3600秒;如果主要是短连接HTTP,保持默认就行。这个值太大会导致连接表里堆积大量过期连接,浪费内核内存,太小则会让正常的慢速连接被误杀。
7.2 连接Hash表大小
LVS的连接查找依赖哈希表,默认大小是2的16次方,即65536条左右的连接跟踪能力。高并发场景下,可以调大这个表:
sysctl -w net.ipv4.vs.conn_tab_bits=20conn_tab_bits=20代表哈希表大小是2的20次方,能支撑约100万个并发的连接跟踪。改这个参数最直观的收益是,当并发数接近表的上限时,不会被哈希碰撞拖慢查找速度。
7.3 内核TCP栈参数
我在调优LVS的时候还会检查这几个参数:
sysctl -w net.ipv4.tcp_tw_reuse=1 sysctl -w net.ipv4.tcp_max_tw_buckets=100000 sysctl -w net.ipv4.tcp_fin_timeout=30tcp_tw_reuse允许新的TCP连接复用处于TIME_WAIT状态的端口,在大量短连接场景下能明显缓解端口耗尽问题。但注意,这个参数在LVS NAT模式下要格外小心,因为NAT模式会修改数据包,开启TIME_WAIT复用可能导致连接状态异常,我实际在NAT模式下遇到过服务无响应的问题,后来关掉就正常了。所以这两个参数要结合模式来判断,不要无脑照抄。
7.4 网卡多队列与RSS
LVS转发性能再高,最终流量还是得从物理网卡进出。单队列网卡在高PPS下会触发软中断瓶颈,表现为单个CPU核心被打满而其他核心闲置。解决方法是用网卡的RSS(Receive Side Scaling)特性,让每个队列绑定不同的CPU核心:
ethtool -l eth0 # 查看队列数量 ethtool -L eth0 combined 16 # 设置为16个队列对于不支持RSS的虚拟机网卡,可以启用RPS(Receive Packet Steering),把收到的包分发到多个CPU软中断中处理:
echo ffff > /sys/class/net/eth0/queues/rx-0/rps_cpus这里ffff是CPU掩码,表示16个CPU。配合中断亲和性设置,LVS在16核机器上跑满线速转发是完全可以做到的。
8. LVS与Nginx、HAProxy的选型对比
聊到这儿,肯定有人会问:LVS这么好,那我是不是就把Nginx扔了直接全上LVS?答案绝对是否定的。这三者在负载均衡体系里根本不是互相替代的关系,而是分层的合作关系。
LVS的优势是纯粹的内核态四层转发,性能天花板最高,但它不理解HTTP协议,无法根据URL做路由,也做不了SSL卸载和HTTP缓存。Nginx的优势恰恰是七层能力:正则路由、Rewrite、反向代理、缓存、gzip、SSL终止都是它的强项,但它每处理一个请求都要经过用户态和内核态之间的多次切换,单机的数据拷贝开销大,性能上限比LVS低。HAProxy则比较折中,它支持四层和七层代理,四层模式在用户态里也算非常优秀的,配置简单灵活,但它同样受限于用户态开销,不敌LVS的内核态转发。
所以真正成熟的架构从来都是分层配合的:
- 流量入口用LVS做四层分发,保证超大规模并发和高可用
- 中间层用Nginx做七层路由,处理URL匹配、静态资源、SSL落地
- 后端连接应用服务,比如PHP-FPM、Tomcat、Go应用进程
我自己在做过一次大型活动的压测时,流量峰值从平时的几千QPS突增到几十万QPS,如果前端只挂Nginx,CPU早就飙红了。但加了LVS在前端,Nginx只需要处理被LVS分发进来的有效业务流量,系统整体稳定得多。那次经历之后,我在架构设计里只要预估流量规模会过万QPS,就一定会把LVS层加上。
9. 那些年我在LVS上踩过的坑:完整排查实录
9.1 VIP始终curl不通,ping也丢
刚配好的LVS,外部访问VIP超时。用ip addr查看,VIP也确实在节点上。后来排查出问题出在RS的ARP抑制没有生效。RS把VIP绑在自己的lo接口上,同时还响应了ARP请求,导致客户端的ARP缓存里存了RS的MAC,数据包直接到RS就绕过了LVS。
这种问题最典型的查法是:在一台客户端机器上执行arp -a,看VIP对应的MAC地址是不是LVS调度器的MAC。不是的话,先去把所有RS上的arp_ignore和arp_announce改对,再清空客户端的ARP缓存重新访问,故障基本都能解决。这个过程我后面每次搭LVS都会第一件事检查,避免浪费一整天抓包排障。
9.2 keepalived主备交替切换,服务间歇性不可用
用keepalived搭完主备之后,发现主节点和备节点的VIP经常在切换,每次切换都有几秒的请求中断。抓虚拟组播报文发现主备都能收到对方的VRRP心跳,但偶尔会丢失几次。最终定位到是交换机的组播泛洪抑制策略把224.0.0.18的组播包丢了。把VRRP的通信改成单播模式,或者给keepalived单独设置一个组播源端口,问题就不出现了。
配置VRRP单播的方式是在实例里加unicast_src_ip和unicast_peer,让心跳走指定的IP单播,不依赖组播网络环境,稳定性更好。具体原理是用单播替代组播,避免依赖网络组播质量,很多云机房环境里组播根本不通,用单向单播模式是个很实用的方案。
9.3 权重已经调低,流量还是往某台机器跑
这是我踩过的一个特别容易让人蒙圈的坑。LVS的wlc算法基于活跃连接数,如果一个RS上有大量长连接一直没有断开,即使它的权重已经调低,算法算下来的比值依然可能比其他RS小,流量该来还是来。解决方法是在ipvsadm里手动清空那台RS的连接,或者把persistence_timeout调成0,让新连接尽快重新分布。
9.4 一台RS宕机后,新连接仍然往里发
keepalived的健康检查机制需要时间发现RS异常并把它摘除。如果RS挂了但keepalived的探测周期内没有及时反应过来,LVS仍会转发一部分流量过去,表现为部分请求直接超时。实际经验是把delay_loop设成3秒,TCP_CHECK的connect_timeout设成2秒,整体检出速度在5秒以内,对大多数业务来说损失就已经很小了。更精细的做法是写一个自定义健康检查脚本,检测HTTP接口状态码,而不是只探TCP端口。
10. 写在最后的一点个人经验总结
LVS这套东西,放在今天来看依然没有过时,原因在于它解决的问题是网络栈层面的“分发”,这个需求永远不会消失。做架构选型的时候,我个人的体会是不要盲目追求最强大的技术,而是找到与业务规模最匹配的工具。几台机器的小集群用Nginx完全够了,甚至系统自带的DNS轮询都能撑住;但如果你的流量在往上走、团队想认真设计高可用架构,把LVS接入进来绝对是一笔划算的投资。
最后分享一个小技巧:维护LVS的时候,一定要养成定期执行ipvsadm -L -n --stats和ipvsadm -L -n -t的习惯,一个看流量统计,一个看连接分布。这两条命令输出的数据就能帮你快速判断流量是否偏斜、哪台RS连接数异常,比上线后到处抓包效率高太多了。希望这篇文章能让你少走一些我走过的弯路。