我最近在整理集群架构相关的技术文档,正好把LVS这一块系统地过了一遍。这个东西吧,光看配置命令其实不难,难的是理解它为什么这么设计——比如为什么DR模式性能最高、为什么需要抑制ARP、为什么调度算法有十种之多。这篇东西我尽量把原理讲透,同时把实际部署中容易踩的坑也一并放进来,希望对正在学习负载均衡或者准备上集群的朋友有帮助。
1. 为什么还要单独讲LVS:它和Nginx反向代理解决的不是同一层问题
在很多初学者的认知里,负载均衡约等于Nginx配置几个upstream,再来个proxy_pass,齐活了。这个理解不算错,但视野确实窄了一些。Nginx工作在七层,处理的是HTTP请求,它看到的是URL、Header、Cookie这些应用层信息;而LVS工作在内核态的四层,它看到的是IP、端口、协议,数据包从网卡进来,通过IPVS模块的规则直接转发给后端,根本不需要经过用户态的进程。
这个差异决定了两个工具的定位:Nginx适合做细颗粒度的流量分发,比如按域名、按路径、按请求头路由,还能做限流、缓存、SSL卸载;LVS适合做超大流量的入口承接,它的吞吐量可以达到百万级并发连接数,而且转发过程中几乎不产生额外的CPU开销,因为它根本不解析报文内容。
在大型架构里,这两者通常是配合使用的:LVS在前面扛流量,后面挂Nginx做七层路由。我见过某公司的生产环境,入口两台LVS用keepalived做高可用,每台LVS后面挂了12台Nginx,Nginx再反代到应用集群。当时压测,单台LVS轻松支撑了每秒几万个并发连接,CPU占用不到10%。说实话用过一次之后,你对负载均衡的理解会完全不一样。
所以这篇博文讲的是LVS的理论,但我不打算只罗列概念,而是把它的设计意图、工作模式、数据包流转细节、调度算法都拆开说清楚,同时附上我在实际搭建和运维中的经验教训。适合刚接触集群的新手,也适合已经有Nginx反向代理经验、想深入了解四层负载均衡的朋友。
2. LVS的三种工作模式:NAT、DR、TUN的原理与差异
2.1 NAT模式:最直观的模式,也是最容易形成瓶颈的模式
NAT模式是三种模式里最容易理解的:客户端请求到达LVS的VIP,LVS通过调度算法选一个后端RealServer,然后把数据包的目标IP改成RealServer的IP转发出去。RealServer处理完请求后,响应的数据包再回到LVS,LVS又把源IP改回VIP,再发给客户端。
这个过程中,请求和响应都要经过LVS,相当于LVS是个完全的中间人。优点在于RealServer可以和其他服务器不在同一网段,甚至可以走公网;缺点也摆在面上:峰值流量一大,LVS的网卡和CPU就成了短板。一个千兆网卡,如果上下行流量都走它,实际能承担的吞吐量就得打半折.注意是进出双向都算。我自己做压测的时候,NAT模式下LVS的CPU占用和网卡流量明显比DR模式高一个量级,虽然不至于崩,但那种看着监控曲线往上蹿的感觉确实不太安心。
NAT模式还有个细节:既然LVS要修改数据包的IP地址,它就一定要开启路由转发功能,内核参数net.ipv4.ip_forward必须设为1。同时IPVS会为每个连接记录状态,确保响应报文能正确回给客户端。如果你在配置NAT模式后发现客户端能连上但没有响应,八成就是没开转发。
2.2 DR模式:生产环境绝对的主流,解决了NAT的瓶颈
DR模式全称Direct Routing,直接路由。它的核心思路是:LVS只负责改数据链路层的MAC地址,不改IP地址。请求进来后,LVS选一个RealServer,把数据帧的目标MAC改成RealServer的MAC地址,然后扔到局域网里。RealServer收到这个数据帧后,发现IP是自己的VIP,于是处理请求,响应的时候直接把源IP设为VIP回给客户端,不再经过LVS。
这就有意思了,响应流量完全绕过LVS,只有请求流量经过它。所以DR模式下LVS的负载压力极小,吞吐量可以比NAT模式高一倍,也是生产环境用得最多的工作模式。但代价是:RealServer和LVS必须在同一个物理二层网络内,而且RealServer的lo接口上必须绑定VIP,同时要抑制对VIP的ARP响应。这里面的坑在于,如果不抑制ARP,交换机就会认为VIP对应多台机器的MAC地址,结果就是后续请求直接被交换机随机分发,LVS的调度逻辑等于白装了。
2.3 TUN模式:IP隧道,用来解决跨机房部署问题
TUN模式是一种很"猛"的做法:LVS把原始请求封装在一个新的IP包里面,通过IP隧道发给远处的RealServer。RealServer解封装后处理请求,响应报文直接发回客户端。它的好处是RealServer可以跨网段、跨机房,甚至部署在不同地域。缺点在于IP隧道本身有封装开销,而且对网络设备有一些要求,很多网络环境不允许自定义IP协议号。
实际工作中TUN模式用得不多。原因是DR模式已经非常够用了,只有当你确实需要把RealServer散布到不同机房时,TUN才会被纳入考量。而且TUN模式下RealServer对内核参数、网络配置的要求比DR模式更繁琐——如果你能把RealServer放到同一网段,就老老实实用DR,没有充分的理由不要上TUN。
2.4 三种模式的对比与选型建议
没有一个绝对好的模式,只有最适合你的环境。下面这张表是我在实际项目中反复权衡后整理出来的:
| 对比项 | NAT | DR | TUN |
|---|---|---|---|
| 后端网关要求 | RealServer网关需指向LVS | 无需修改网关 | RealServer独立网关 |
| 响应流量是否经过LVS | 是,严重占用带宽 | 否,吞吐量最高 | 否 |
| RealServer与LVS网络要求 | 可跨网段 | 必须在同一二层网络 | 可跨网段 |
| RealServer是否绑定VIP | 不需要 | 需要,同时抑制ARP | 需要 |
| 配置复杂度 | 低 | 中 | 高 |
| 典型场景 | 中小规模集群 | 大规模高吞吐业务 | 分布式异地机房 |
我的建议是:如果你刚接触LVS,先用NAT模式跑通一个小型实验环境,理解数据包的流向变化。一旦进入生产环境或者要做大规模压测,直接用DR模式,没有任何悬念。TUN模式除非你真有跨机房的强需求,否则别自己给自己找麻烦。
3. 为什么DR模式必须配置这条命令:ARP抑制机制详解
3.1 一个真实的排障案例
有一年我给某公司搭一套DR模式的负载均衡,LVS和后端两台RealServer都配置好了,vip绑定也没问题,ipvsadm规则看起来也正常,但压测的时候发现交换机转发的流量时好时坏,后端服务偶发不可用。当时我第一反应是调度算法问题,换个算法,现象依旧。排查了大半天,最后才意识到是ARP响应没抑制干净。
这个问题的本质是:VIP同时存在于LVS的eth0口和两台RealServer的lo接口上。当交换机发出ARP请求询问VIP对应的MAC地址时,三台机器都可能应答,交换机就会把VIP的MAC地址表项反复刷新,最终指向一个错误的端口。客户端发来的连接请求根本不会到LVS,直接被交换机送到了某台RealServer上,调度策略完全形同虚设。
3.2 ARP机制与VIP地址冲突原理
ARP协议解决的是"已知IP,如何找到MAC地址"这个问题。局域网里的设备靠MAC地址通信,当设备A要发数据给设备B时,它先广播一个ARP请求:谁的IP是192.168.1.10?请告诉你的MAC地址。设备B收到后响应,A再记录到ARP缓存表里,后续数据帧就可以直接发到B的MAC了。
在DR模式下,LVS的VIP和所有RealServer的VIP都在同一广播域里竞争。RealServer的lo接口上绑定了VIP,它看到ARP请求时,就会响应自己的MAC地址,于是交换机的MAC表开始混乱:同一个VIP对应的MAC地址在多个端口之间抖动。这个问题,业界标准的解法就是让RealServer对VIP的ARP请求保持沉默,只由LVS来应答。Linux内核里对应两个关键参数:arp_ignore和arp_announce。
3.3 推荐的sysctl配置与解释
DR模式在RealServer上的经典配置,我直接给出并解释每行的含义:
# 备份原配置 cp /etc/sysctl.conf /etc/sysctl.conf.bak # 追加以下内容 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 = 2arp_ignore = 1的意思是:只回答目标IP是本机网卡IP的ARP请求。对于VIP来说,它绑在lo接口上,属于"本机地址",响应逻辑依然会触发,但区别在于——配合下面的arp_announce = 2,系统对外宣告IP时,不会把lo接口上的VIP作为源IP去宣告。arp_announce = 2的意思是:总是使用最合适的本地接口IP来作为ARP通告的源。网卡在对外发送ARP请求或回应时,尽量用普通IP而非VIP,从而把VIP的应答权独占交给LVS。
配置完成后,还需要重启网络服务或执行sysctl -p,并建议用ip addr show lo和arp -n验证本地VIP是否还在。这里有个容易被忽略的点:arp_ignore和arp_announce的值,必须同时在all和对应网卡上设置,否则部分内核版本可能有不生效的情况。实际踩坑后我的固定操作是:不管三七二十一,all和eth0、lo全部写一遍,宁可重复不能遗漏。
3.4 验证是否抑制成功的方法
配置完成后,需要验证。最简单的办法是:在LVS这台机器上执行arping -I eth0 -c 2 192.168.1.10,看只有哪台机器回应。如果只有LVS回应,说明RealServer的抑制是成功的。或者直接在RealServer上执行ip addr show lo,确认VIP在lo接口上,再执行arping观察每台机器的响应情况。
真实排障时还有一招:临时禁用RealServer上的网卡或停掉VIP,再压测观察流量是否恢复。如果流量立刻全部集中在LVS上了,那说明之前的ARP混乱确实是RealServer造成的。这个方法适合做快速二分定位,不需要抓包也能确认问题范围。
4. LVS的十种调度算法:静态调度与动态调度
4.1 静态调度:不关心后端实时负载,只按固定规则分发
静态调度算法在建立连接时按事先定好的规则决定后端,不会动态参考当前各服务器的真实负载。它们的特点是响应快、开销小,适合后端配置比较接近、性能差异不大的场景。
轮询调度(RR)是最基础的:请求按顺序一台一台分配给后端,1、2、3...然后再回到1。它假设每个RealServer的性能是等价的,但现实世界里通常不是这样——某台服务器可能因为历史请求堆积,已经扛了很大压力,RR照样会给它分配新请求。加权轮询(WRR)比RR多了一个权重参数,你可以在配置时说明"这台机器性能好,能承担3倍流量",调度器就会按权重比例分配,但它仍然不感知后端真实的连接压力。
最少连接调度(LC)开始聪明了一点:它记录每台后端当前的活跃连接数,新请求分配给连接数最少的那台。加权最少连接(WLC)在LC基础上再叠加权重,计算时同时考虑了连接数和权重值,这是生产环境中我个人用得最多的算法,因为它在"实时感知压力"和"兼顾硬件差异"之间取得了不错的平衡。
除了上面这些,还有基于目标地址散列(DH)、基于源地址散列(SH)两种算法,都是通过哈希把同一个IP的请求固定分发到同一台后端。这种方式在需要保持会话的场景里非常好用,缺点是一旦后端宕机或权重变化,会影响大范围的连接路由。
4.2 动态调度:实时收集后端状态,自适应调整分发策略
动态调度算法不像静态算法那样只根据预设的规则做决策,它会收集每个RealServer的实时状态,再把这些状态代入公式计算,最终决定连接分给谁。
最典型的代表是sed(最短预期延迟)算法。它计算每个连接的预期处理时间,选择预期延迟最小的后端。核心逻辑是:(当前活跃连接数 + 1)除以权重,选择结果最小的那台。这比LC又进了一步:LC只看当前连接数,SED会把权重因素也放进同一个公式里做归一化比较,结果对后端处理能力差异较大的集群更友好。
nq(永不排队)算法则更进一步,它直接检查每台RealServer是否有空闲连接,只要发现一台没有活跃连接,就立即把请求发过去;只有当所有后端都已经在忙时,再按SED的思路进行选择。我自己的体会是,nq算法在响应时间要求极高的场景下表现突出,因为它最大限度减少了排队等待。但因为它的实现逻辑更复杂,配置后需要多观察一段时间,确保和业务模型契合。
还有两类偏门算法:LBLC(基于局部性的最少连接)和LBLCR(基于局部性的带复制的最少连接)。它们是针对缓存服务器场景设计的,基本套路是"同一个目标地址的请求尽量送到同一台后端",同时在某一台后端过载时允许部分请求溢出,从而提高缓存命中率。这两类算法如果你不是在做缓存集群,几乎用不到,了解即可。
4.3 超时连接和持久性配置在LVS中的特殊处理
LVS还有一个容易被忽略的点:ipvsadm命令的-p参数用于启用持久性连接,它可以让同一个客户端的多个请求在一定时间内始终发送到同一个后端,这个和免登录保持会话的业务需求紧密相关。在多机部署无状态应用时,LVS加上持久性会削弱负载均衡本身的分散效果,所以是否开启取决于业务是否需要粘性会话。
如果你必须同时开启持久性和动态调度,我通常会做一层取舍:把持久时间压到600秒以内,让长时间不活跃的客户端请求尽快迁移到其他后端。如果这个线上业务无法接受会话丢失,那最好还是改进应用层,使用分布式会话存储,而不是在LB这一层硬撑。
4.4 十种算法的选择建议
表单直接给结论,方便读者对照选用:
| 算法 | 核心逻辑 | 适用场景 | 实战推荐度 |
|---|---|---|---|
| RR | 顺序轮询,一视同仁 | 后端配置几乎一致、请求时长接近 | 中等 |
| WRR | 按权重轮询,考虑性能差异 | 后端配置差异明显 | 中等偏高 |
| LC | 最少连接数 | 后端请求耗时差异大 | 高 |
| WLC | 加权最少连接 | 后端配置、负载均不均 | 最高,最常用 |
| SED | 最短预期延迟 | 请求耗时相差大的场景 | 高 |
| NQ | 永不排队,有闲即发 | 响应时间敏感的实时业务 | 中高 |
| DH | 按目标地址哈希 | 后端做缓存服务 | 低 |
| SH | 按源地址哈希 | 需要会话保持且无共享存储 | 低 |
| LBLC | 局部性最少连接 | 后端是缓存服务器 | 低 |
| LBLCR | 局部性带复制最少连接 | 高命中率缓存集群 | 低 |
我自己的惯例是:没有特殊要求直接用WLC,因为它在绝大多数业务下表现稳定,不会出大错。如果你的业务对响应延迟要求极其苛刻,可以尝试nq,但一定要搭配监控系统,观察一段时间后再定型。哈希类的算法除非有明确的会话保持需求,否则我不建议新手优先尝试,因为一旦权重调整,连接路由的变动会让排查变得复杂。
5. 集群架构组成与高可用设计:从理论到落地
5.1 LVS集群的三个核心成员及其职责
一个完整的LVS集群通常由三部分组成:负载调度器(Director)、后端真实服务器池(RealServer)、共享存储系统。
调度器是集群的入口,运行LVS内核模块,通过IPVS规则掌控流量分发。调度器本身的性能瓶颈几乎可以忽略(DR模式下只处理入站连接请求),但它的高可用性却至关重要——调度器挂了,整个集群就瘫痪了。生产环境里一般配两台调度器,用keepalived做故障切换。
RealServer是实际提供服务的服务器,通常是一个相同应用的集群。它们通过VIP对外提供服务,但各个RealServer自己的IP是独立的,需要将VIP绑定到各自的lo接口上。
共享存储系统主要解决"所有RealServer需要访问相同数据"的问题——比如文件上传、静态资源等场景。通常用NFS或分布式文件系统来实现。但这里要说一句:如果你的业务是无状态的(比如纯计算类API),那共享存储则不是必须的。分布式文件系统在带来方便的同时也引入了新的性能开销和单点故障风险,到底要不要上取决于业务是否强依赖本地文件。
5.2 双Director高可用架构实践
在实际部署中,我通常在用户前端放两台Director,一主一备,共享同一个VIP。在正常情况下,VIP绑定在主节点上,请求全部由主节点分发到RealServer;主节点故障时,备节点通过keepalived的VRRP协议感知到异常,自动把VIP和IPVS规则切换到备机,这一过程通常是秒级的。
这里有一个重要细节:keepalived不只负责VIP漂移,还要同步IPVS规则。如果主节点运行期间手动添加了新的后端RealServer,而备机上没有同步这些规则,主节点宕机后备用节点接手时,新加入的RealServer就完全不可用,甚至更奇怪:VIP漂过来了,但调度器还在给已经不存在的后端转发连接。所以keepalived的配置里一定要开启规则同步功能,主备之间要定期用ipvsadm -S导出规则、用ipvsadm -R导入规则,或者直接依赖keepalived的通知脚本自动同步。
5.3 健康检查策略:丢包重试与主动探测
LVS本身不像Nginx那样内置一个health_check配置项,它的健康检查主要靠外部组件:keepalived或者ldirectord。keepalived会对每个RealServer做TCP或HTTP健康探测,一旦发现某个RealServer不响应,就自动把这一条规则从IPVS中摘除,不再派发新连接;当它恢复后,再把规则加回来。
我遇到的一个真实教训是:健康检查不能只做端口探测。某次后端应用因为数据库连接池耗尽,进程虽然活着,端口也正常监听,但请求进来全部超时。端口探测显示"正常",LVS照样往这台RealServer派流量,最后是调用方报故障我们才排查到根因。后来我把健康检查改成HTTP层面探测,用一个专门的轻量URL做检测,比如/healthz,规定2秒内响应且返回200才视为健康,这个问题就彻底解决了。
5.4 会话保持与持久性的合理配置
LVS的会话保持主要靠-p参数和调度算法的取舍。比如电商业务,用户从加购物车到结算可能需要几分钟时间,中间多次请求如果被分发到不同RealServer,而Session没有同步到共享存储,就会出现"加购成功,结算时购物车空了"的诡异问题。
最优雅的方案是把Session放到Redis或数据库层面做共享,这样RealServer根本不需要会话保持。可如果历史包袱重,应用改造无法一步到位,LVS层面加-p参数撑一段时间也是有的。我的经验是:如果是临时方案,-p 3600可以接受;如果是长期方案,一定要做压测,因为持久性开了之后,RealServer之间的负载均衡效果会大打折扣。
5.5 一个可参考的小规模集群落地配置
这里给一个最小化的DR模式配置示例,方便你照着操作和理解整个链路。
LVS节点配置(假设VIP为192.168.1.100,LVS的IP为192.168.1.101,RealServer分别为192.168.1.11和192.168.1.12):
先安装工具(以CentOS系为例):
yum install ipvsadm keepalived -y配置IPVS规则:
# 开启转发(DR模式其实不需要,但开着无妨) echo 1 > /proc/sys/net/ipv4/ip_forward # 添加VIP ip addr add 192.168.1.100/24 dev eth0 # 添加调度规则:使用wlc算法,持久性600秒 ipvsadm -A -t 192.168.1.100:80 -s wlc -p 600 # 添加后端RealServer,使用DR模式 ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.11:80 -g ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.12:80 -g # 查看规则确认 ipvsadm -L -nRealServer配置(以192.168.1.11为例,假设应用监听80端口):
# 在lo接口上绑定VIP,注意netmask是32位 ip addr add 192.168.1.100/32 dev lo # 调整ARP抑制参数 sysctl -w net.ipv4.conf.all.arp_ignore=1 sysctl -w net.ipv4.conf.all.arp_announce=2 # 停用lo接口上的ARP响应(推荐再配一个路由规则) ip rule add from 192.168.1.100 table 100 ip route add local 192.168.1.100/32 dev lo table 100最后这段话是给新手提醒:DR模式下,RealServer的lo接口绑定VIP的掩码一定是/32,因为VIP在lo上只是一个"标识",用来接收来自LVS的请求帧,而不是真想让这个VIP响应局域网广播。如果你用/24绑上去,很可能引发ARP广播风暴,网络里其他机器的ARP缓存会全部被这个VIP刷一遍,后果相当严重。
6. 从理论走向实操:一些调试工具和验证技巧
6.1 查看连接跟踪和转发统计
无论你用的是keepalived还是直接操作ipvsadm,线上验证连接是否被调度到正确的RealServer,最简单的方法就是看连接表:
ipvsadm -L -n --stats这个命令会列出IPVS当前的连接数、包数、字节数。如果你压测后某个RealServer的计数明显高于其他机器,要么是调度算法的权重设置有问题,要么是健康检查把这个节点之外的流量全打到它身上了。我在排查类似问题时,通常先用--stats确认流量分布是否合理,再用ipvsadm -L -n --timeout检查连接超时时间是否过长,避免无效连接长期占用调度表项。
6.2 直接抓包验证数据包走向
有时候感觉链路不对,但配置看起来都正常,那就用tcpdump抓包。分别在LVS和RealServer上抓一下:
# LVS上抓VIP的80端口流量 tcpdump -i eth0 host 192.168.1.100 and port 80 -n # RealServer上抓本机接口流量 tcpdump -i eth0 host 192.168.1.100 and port 80 -n对比两次抓包结果:LVS上应当能抓到来自客户端的请求包,以及转发出去的帧(注意在DR模式下目标MAC是RealServer的MAC);RealServer上应当能抓到同样IP的请求包,并能抓到回给客户端的响应包。如果RealServer上只看到了请求包,没有响应包,那问题基本出在响应路由上——很可能是RealServer没有设置网关,或者VIP没有被正确绑定到lo接口。
6.3 高并发下的调优参数
生产环境里,你还需要关注几个内核参数。虽然LVS转发本身很高效,但前置TCP栈的优化仍然重要:
# 提高文件句柄数限制 ulimit -n 655350 # 内核TCP连接队列调大 sysctl -w net.core.somaxconn=65535 sysctl -w net.ipv4.tcp_max_syn_backlog=65535 # 启用TIME_WAIT复用,缓解短连接大量TIME_WAIT sysctl -w net.ipv4.tcp_tw_reuse=1 sysctl -w net.ipv4.tcp_timestamps=1 # 端口范围调大,避免客户端大量连接时端口不足 sysctl -w net.ipv4.ip_local_port_range="1024 65535"这些参数我并不是一次性全改完的,而是每次压测发现瓶颈后逐步调整,改一个测一个。tcp_tw_reuse这个参数尤其要谨慎,它要求两端都启用TCP时间戳,如果你的环境里有老旧系统,可能会引起连接建立异常。
6.4 压测时的常见误判与注意事项
压测LVS和压测普通Web服务有一个明显不同的点:LVS的模式决定了你的响应包路径。如果你用DR模式压测,压测机如果和LVS、RealServer在同一网段,数据包是直接从RealServer回给压测机,这没有问题。但如果压测机和后端不在同一网络,RealServer的回应包就必须借助网关,如果你没有配置好RealServer的默认路由,压测结果就会惨不忍睹。但这不是LVS的性能有问题,而是你的网络拓扑从设计上就不符合DR模式的使用前提。
因此,我的建议是:正式上压测之前,先在实验环境里跑通一个最简单的主备集群,用curl验证请求能够被正确分发到两台RealServer上,再逐步加大并发,观察调度器的CPU、内存、网卡流量,以及RealServer各自的连接数变化。这个流程看起来繁琐,但能在真正遇到问题时帮你省下大量定位时间。
7. 写在最后:一点个人体会
LVS这玩意儿,配置命令其实就那几条,半天就能学会。它真正的门槛在于网络原理的理解——TCP/IP协议栈、ARP机制、数据链路层的转发逻辑,以及这些底层机制和上层业务之间的相互影响。我见过很多朋友照着网上的博客配完了LVS,线上流量一大就出问题,最后灰头土脸地退回到Nginx单机模式,其实不是LVS不行,而是对原理的理解缺了一块。
我自己踩过最深的坑就是DR模式下的ARP问题,那次排障给我留下的印象实在太深了。你永远想不到一个小小的ARP广播,能把整个集群的流量搅得天翻地覆。但这也正是LVS有意思的地方:理解了它,你对网络协议栈的认知会上一个台阶,再看别的网络技术,很多原来觉得玄乎的东西都会豁然开朗。
如果你正准备在生产环境引入LVS,我的建议很简单:先在实验环境用DR模式搭一套最小集群,故意制造几次故障(停掉某台RealServer的服务、重刷规则、模拟主Director宕机),亲手感受一下切换过程,再上生产。理论学到什么程度都不如这一天实操来得扎实。