news 2026/10/13 3:43:01

LVS核心原理与生产实践:DR/NAT/TUN模式、调度算法及keepalived高可用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LVS核心原理与生产实践:DR/NAT/TUN模式、调度算法及keepalived高可用

大概在两三年前,我帮一个朋友排查线上网关的问题:后端几十台实例几乎全部处于忙碌状态,可前置的负载均衡层CPU负载却始终占不满。讨论一圈才确认,朋友用的是一台跑着LVS类方案的机器,流量入口走的是Linux内核里的IPVS转发框架。那时我才意识到,今天还能在各种架构图里看到“LVS负载均衡”这个名词,不是因为它老,而是因为它在四层转发这件事上的效率,确实没有几个方案能比得上。这篇文章我会系统拆解LVS的核心原理:三种工作模式到底差在哪里、调度算法各自适合什么场景、keepalived怎么配合它做高可用集群,以及我在生产环境里反复踩过、也帮别人处理过的那些坑。对正在做集群架构选型、或者已经被LVS绕晕的研发和运维同学,这篇应该能直接拿来当参考。

1. 先把位置摆正:LVS是内核里的转发框架,不是跑在用户态的代理程序

1.1 一个最常见的误解:为什么找不到LVS进程

接触LVS后最容易被绕晕的地方,就是“进程去哪了”。按常识,运行一个负载均衡软件,至少会有一个进程挂在那里,比如开源七层代理会有自己的工作进程,监听端口、接受连接,一眼就能看到。但LVS完全不一样:转发逻辑在Linux内核的IPVS模块里,它平时不占用固定端口,也不会常驻一个用户态进程。你执行ps -ef看不到它,执行netstat -lntp也看不到它,于是很多刚上手的人会以为自己没装好。

实际上,工作正常的LVS集群,你唯一能通过交互看到的东西,是用ipvsadm命令刷出来的规则和统计信息。ipvsadm是用户态管理工具,它通过netlink接口把配置下发到内核,内核里的IPVS框架真正负责转发。理解了这个分层关系,后面所有排查思路才能建立起来。LVS不是“一款软件”,更像“内核里的一个功能开关”,配合外部工具去驱动它。

1.2 一条数据包在IPVS内部是怎么走的

当客户端请求到达LVS服务器的网卡,经过链路层接收后会进入netfilter框架的钩子点,IPVS模块就在这些钩子点注册了自己的处理逻辑。处理流程可以简化成三步:先看数据包的目的IP和端口是否匹配某个虚拟服务;匹配成功后就查连接跟踪表,如果这条连接已经存在,就按表项记录直接改包转发;如果是一条全新连接,就用配置好的调度算法选一台后端真实服务器,把新连接信息写进连接跟踪表,再执行转发。

这里说的“改包”,在不同工作模式下改的位置不一样。NAT模式修改目的IP,DR模式修改二层目的MAC,TUN模式则是在IP层再做一次封装。但不管改哪里,整个过程都在内核态完成,没有用户态和内核态来回切换的额外开销,也没有HTTP解析、日志写入这类高消耗动作。这就是LVS在四层转发上效率极高的根本原因。

1.3 LVS和七层代理之间,实际的边界在哪里

我之前参与过的一次压测,能很直观地说明这个问题。某个业务入口用开源七层代理扛流量,压力一起来,CPU先到顶,因为每个请求都要经历用户态和内核态的多次切换,还要做HTTP头部解析、连接管理、超时处理。后来把入口改成LVS做四层转发,后端RS几乎没变,前置机CPU占用率立刻掉了一大截。

七层代理擅长的是“看懂请求内容”:按URL、Cookie、Header做路由,做鉴权、改写、限流。LVS完全不在乎这些,它只关心四层以内的信息——IP、端口、协议类型。所以边界很清楚:需要精细控制请求,用七层;需要扛极端并发、海量新建连接、对协议透明,用四层。很多大型系统实际是两层叠加,LVS在入口先把大部分流量按IP分给后面的七层集群,再由七层做更细的转发。

1.4 高可用不是LVS自带的能力,别混淆keepalived的职责

还有一个概念要提前捋清楚:LVS本身只提供转发机制,不提供健康检查,也不提供虚拟IP自动漂移。生产环境里常说的“LVS高可用”,其实是叠加了keepalived实现的。keepalived借助VRRP协议让主备两台机器共享同一个VIP,主节点故障时备用节点接管VIP,同时由它对后端RS做健康检查,把故障机器从转发列表里自动摘除。明白了这个架构,就不会在排查问题时找错方向:LVS负责数据面,keepalived负责控制面。

2. DR、NAT、TUN三种模式的选择:核心是流量回程成本放在谁身上

2.1 先分清“进”和“回”两条路径

评估负载均衡方案时,很多人只盯着请求进来那一段,却忽略了响应返回这一段。一个完整的TCP会话有两条路径:客户端到LVS再到后端RS的“进向路径”,以及RS处理完响应后回到客户端的“回向路径”。不同工作模式最大的差异,其实就在于回向流量由谁来承载。

一般来说,谁承载回向流量,谁就是系统最大的瓶颈。如果回向流量也要经过负载均衡器,那么负载均衡器的带宽、连接表容量、CPU处理能力都会被放大一倍的消耗。理解了这一点,再看三种模式的取舍就会很清晰。

2.2 NAT模式:支持端口变化,但回程必须绕回LVS

NAT模式的转发逻辑比较直观:客户端请求到达LVS后,LVS会把目的IP从VIP改成后端RS的真实IP,同时可能做源地址转换,把源IP改成LVS自己的内网IP。RS处理完请求后,把响应包回给LVS,LVS再把源IP改回VIP,送回客户端。

这种模式最大的特点,是支持端口映射。虚拟服务的端口和后端RS的端口可以不一致,因为LVS在转发时会把端口一并改写。这在某些需要兼容旧服务端口的场景里很好用。

但代价同样明显:所有回向流量都必经LVS。假设客户端下载一个大文件,LVS不仅要在进向承担请求,还要在回向承载几乎等于文件体积的响应流量。流量一大,LVS自身的网卡带宽、连接跟踪表、CPU都会先被压垮。所以NAT模式适合后端RS数量不多、整体吞吐量可控、需要端口映射或后端网络隔离的场景;一旦规模上去,第一个该换掉的就是它。

2.3 DR模式:改的是MAC,出包直接从RS回客户端

DR模式的做法,是把数据包的二层目的MAC改成后端RS网卡的MAC,源IP、目的IP都不动。RS收到包后,发现目的IP是自己在回环接口上配置的VIP,于是正常接收并处理,然后把响应包通过自己的物理网卡直接发给客户端,完全不经过LVS。

这就是DR模式性能高的根本原因:进向流量压在一个点上,回向流量分散到所有RS各自承担,LVS的带宽压力一下子少了一半。因此DR模式是三类模式里生产环境用得最多的。

但它有两个硬性约束。第一,LVS和RS必须在同一个二层广播域内,因为MAC地址转发依赖ARP,出了二层就找不到RS的MAC。第二,RS必须愿意接收目的IP是VIP的包——需要在每台RS的回环接口上绑一个VIP,同时要抑制RS对外应答VIP的ARP请求。否则整个网络里会有多台机器声称自己拥有这个VIP,ARP表一乱,转发就彻底没法看。

2.4 TUN模式:IP层再套一层,解决跨网段问题

TUN模式的做法,是把原始数据包整体封装进一个新的IP包里。外层包的目的IP是后端RS的真实IP,内层包保留原来的客户端IP和VIP。RS收到外层包后解开封装,取出内层包正常处理,响应包直接通过自己的路由发回客户端,不回LVS。

因为不依赖MAC转发,TUN模式天然支持跨网段,后端RS甚至可以分布在不同机房。这算是它和DR模式最大的区别。

但TUN要付出额外代价:后端内核需要支持隧道封装模块;封装会带来额外的IP头部开销,可能导致整体包长超过MTU,一旦触发分片,性能会明显劣化;而且封装和解封装虽然在内核完成,毕竟比直接改MAC多了一层处理。实际生产里,除非有明确的跨机房分布式需求,否则大多数团队还是优先选DR,把TUN留给比较特殊的场景。

2.5 三种模式的选型对照

模式转发手段回程是否经过LVS端口映射网络要求典型场景
NAT修改目的IP/端口是支持可跨网段后端数量少、需要改端口、后端网络隔离
DR修改目的MAC否不支持同二层广播域高并发同机房,生产中最常见
TUNIP封装否不支持可跨网段跨机房、后端RS规模很大

从这张表能看出一个规律:越往后,回程越轻,转发效率越高,但对网络环境的约束也越特殊。没有哪种模式是绝对碾压的,一切取决于你的后端RS能不能满足对应的网络前提。

3. 调度算法选型:每一种都在和“连接状态”博弈

3.1 先看懂IPVS的连接表

LVS能支撑高并发,除了转发路径短之外,还靠一张庞大的连接跟踪表。每一个新建连接进入时,IPVS都会查表:如果表里已有这条连接的记录,就直接按记录继续转发;如果不存在,才调用调度算法选一台RS,并把连接关系写进表里。

这个机制决定了调度算法只作用在“新建连接”的一瞬间。一旦连接被建立,后续所有数据包都按表项走,不会再重新调度。所以算法好不好,核心看它在短时间内处理大量新连接时,能不能把负载相对均匀地分出去。

3.2 轮询与加权轮询:简单可控,但前提是后端足够匀称

轮询(rr)就是一台接一台地轮流分配,加权轮询(wrr)在后端权重不同的情况下,按比例分配新连接。这类算法逻辑最简单,调度开销也最小。

适合的场景,是后端RS规格基本一致、请求处理时间接近、连接存活时间不悬殊。比如都是同配置的云主机,跑的都是无状态短接口,rr或wrr就足够用了。但要注意,加权轮询里的权重怎么定有讲究。很多人按内存大小配权重,实际上对负载均衡来说,CPU核数、网卡带宽、业务主要消耗的资源类型才是更关键的参考指标。权重配错了,高配机器闲死,低配机器忙死的情况我见过不止一次。

3.3 最少连接与加权最少连接:默认选择,但也最容易被误读

最少连接(lc)会挑当前连接数最少的那台RS,加权最少连接(wlc)再加一层权重计算,用连接数和权重的比值做比较。IPVS的默认调度算法通常就是wlc,这也是很多人一上来就在用的配置。

但“连接数最少”不等于“负载最低”。举个反例:假设RS-A上挂了10条连接,每条都是在处理一个需要几十秒的慢查询;RS-B上挂了20条连接,但每条都是毫秒级返回的短事务。按连接数看,RS-A更空闲,实际业务压力却可能比RS-B大得多。所以wlc适合大量短连接、请求处理时间相对均衡的场景;如果业务里长连接和慢请求占比高,只看连接数会失真,不如直接用加权轮询来得可控。

3.4 哈希类算法:源地址哈希和目标地址哈希的取舍

源地址哈希(sh)会把来自同一个源IP的请求固定分配到同一台RS。这种策略天然实现了会话保持——同一个用户总是落在同一台机器上,不需要额外的中间存储来记录会话归属。

目标地址哈希(dh)则是按目的IP做哈希,通常用于正向代理或缓存类业务,让同一个目标地址的请求集中到固定RS,有利于提升本地命中率。

哈希类算法的隐患在于,一旦RS列表发生变化,哈希映射会重新分配,大量存量连接可能被整体打散。如果业务无法容忍这种情况,要配合VIP的持久性参数来做粘滞,给连接留出足够的存在时间,而不是频繁变更RS列表。

3.5 局部性算法和延迟类算法:知道存在就好,别轻易上用

lblc(基于局部性的最少连接)和lblcr(带复制的基于局部性最少连接)是面向Cache类场景设计的,目标是尽量把同一目标IP的请求调度到最近使用过的那台RS,提升缓存命中率。sed(最少期望延迟)、nq(不排队)等算法会结合服务器权重和当前连接数做更复杂的估算。这些算法在特定场景确实有设计上的合理性,但生产环境里配置复杂、行为不如前几类可预期,我一般不建议轻易使用。真正跑得最多的,还是wlc、wrr、sh三种。选型可以参照下面的思路:

场景推荐算法原因
后端规格一致、请求简短rr / wrr简单可控,调度开销小
后端规格不一致、短连接为主wlc能动态平衡负载
长连接、需要会话保持sh + persistence同一来源始终落同一台
缓存类业务dh / lblcr提高本地命中率

4. keepalived + LVS:把单点入口变成可切换的高可用集群

4.1 为什么单台LVS注定扛不起生产环境

LVS本身的转发能力再强,也只是单点。它所在的服务器一断电、一断网,整个入口就瘫了。没有高可用方案的话,恢复流程只能靠人工改IP或者重新配置,业务中断时间以小时为单位,这是生产环境绝对不能接受的。

keepalived解决的就是这个问题。它通过VRRP协议,让两台或更多LVS节点共享同一个VIP:正常情况下VIP落在主节点上,主节点故障后,备用节点会自动抢占VIP。对客户端来说,访问的还是同一个IP,完全感知不到后端的切换。

4.2 一份可以直接改用的最小配置

下面是一份主节点侧的keepalived配置,结构清晰,可以直接替换IP改用于自己的环境:

global_defs { router_id lvs_master } vrrp_instance VI_LVS { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 192.0.2.10/24 } } virtual_server 192.0.2.10 80 { delay_loop 6 lb_algo wlc lb_kind DR protocol TCP real_server 192.0.2.11 80 { weight 1 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 2 } } real_server 192.0.2.12 80 { weight 1 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 2 } } }

备用节点的配置基本一样,区别在于state改成BACKUP,priority设低一些,比如95。这里有几个值得注意的细节:virtual_router_id在主备两台机器上必须一致;auth_pass也要求一致;interface要确认填的是承载VIP网段的实际网卡名,填错了VIP根本起不来。

4.3 健康检查的配置细节:TCP握手成功不等于服务正常

配置里的TCP_CHECK是keepalived最常见的健康检查方式,原理就是做一次TCP三次握手。握手成功,就认为后端RS是健康的。这种检查成本低、不会干扰业务,但只能证明端口还在监听,证明不了应用真的能正常处理请求。

如果业务是HTTP服务,更合适的探活方式是HTTP_GET,让keepalived发一个HTTP请求去匹配状态码:

HTTP_GET { url { path /healthz status_code 200 } connect_timeout 3 }

这里的connect_timeout和nb_get_retry配置要小心。设得太短,后端稍微响应慢一点就会被误判成故障,在业务高峰期误摘会导致流量雪崩;设得太长,后端真的挂了也要等很久才能摘除,故障时间被拉长。一般connect_timeout用3秒、重试3次、间隔2秒是比较常见的起点,后续根据业务耗时分布再微调。

4.4 高可用是否生效,至少要从四个角度验证

配置改完,不要急着觉得万事大吉。我习惯按下面四个步骤做验证:

  • 在主机上执行ip addr show eth0,确认VIP绑定正常;在备机上确认同一时刻看不到VIP。
  • 执行ipvsadm -L -n --stats,确认转发规则在、每个RS的连接统计在涨。
  • 停掉一台后端RS,观察它是否在delay_loop周期内从转发列表消失,重新启动后再观察它是否自动回来。
  • 模拟主节点故障,比如停掉keepalived服务,确认VIP能自动漂移到备用节点,且业务流量不中断。

这套流程走下来,比任何配置截图都让人放心。

5. 生产环境翻车实录:那些几乎每个LVS集群都会遇到的细节

5.1 DR模式第一坑:RS抢答VIP的ARP请求

DR模式下最常见的问题,就是RS对外响应了VIP的ARP请求。后果是网络上对VIP的ARP解析出了多台机器,发给LVS的流量被RS“抢答”走,连接异常、丢包、不通的问题会一起冒出来。

解决办法是,在每台RS的回环接口上绑定VIP,同时抑制ARP应答。基础配置是这样的:

ip addr add 192.0.2.10/32 dev lo # /etc/sysctl.conf 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

arp_ignore=1表示只回答目标IP是本接口IP的ARP请求,其他接口一律不答;arp_announce=2表示尽量使用接收ARP请求的那个网卡的最优地址来应答,避免把lo:0上的VIP暴露出去。改完记得sysctl -p生效。还有一个细节容易忽略:RS上VIP的掩码一定写成/32,写成/24之类的掩码,会导致路由表异常,后续排查能把人折腾疯。

5.2 后端长连接与优雅摘除:改配置不等于断连接

LVS对已经建立的连接是“懒管理”的。keepalived把某台RS从转发列表里摘除,只是让新连接不再调度过去,已经建立的TCP连接会继续存在,直到连接自然结束或被业务方主动关闭。这个特性在维护长连接服务时特别容易出状况——你明明已经把RS下线了,业务侧却还在持续报错,因为存量连接还在往那台机器上打流量。

更稳妥的做法是先摘“流量”再摘“机器”。通过ipvsadm -e把目标RS的权重改成0,让新建连接不再进来,等存量连接全部结束后再关停服务。很多有经验的运维会把这个动作写成交替脚本,配合业务方的连接优雅关闭机制,避免直接kill进程造成请求被硬切。

5.3 连接超时参数:短连接高压下的隐性瓶颈

IPVS的连接跟踪表是有超时时间的,默认值在某些场景下偏长。比如大量短连接业务里,一个请求几百毫秒就结束了,但表项可能还要保留很长时间。连接数一多,无意义残留会把整张连接表占满,新连接反而建不进来。

可以通过ipvsadm -L -n --timeout查看当前超时参数,必要时调整内核参数。比如net.ipv4.vs.timeout_tcp、net.ipv4.vs.timeout_tcp_fin、net.ipv4.vs.timeout_udp这些值。调低空闲超时能释放表项,但切忌盲目压得特别低,否则还在正常传输的大流量连接可能被误回收,表现为莫名其妙地断流。我一般会先观察业务连接存活时间分布,再按P99的值留出余量来设定。

5.4 网卡多队列与软中断:压测起来问题往往出在包接收上

LVS对CPU逻辑计算的消耗不高,但网卡收包和发包会触发软中断。如果Linux默认把所有中断都压到0号CPU上,流量一旦上来,单核软中断占比会拉满,业务看着没跑满,吞吐却上不去了。

可以给多队列网卡开启RSS,让不同队列的中断分散到不同CPU核心;硬件不支持多队列的,可以配置RPS(Receive Packet Steering),把收包软中断分摊到多个CPU。另外,irqbalance服务通常建议保持开启,让它自动平衡中断,比人工指定一两个CPU省心得多。压测验证时,用top按1看每个核心的软中断占比,如果只有一个核红了,基本就是这个环节的问题。

5.5 变更前留一份“正常状态”快照

最后分享一个我自己的习惯:任何LVS或keepalived变更之前,先执行一次ipvsadm -Ln > /tmp/ipvs_before.txt,把规则和权重快照留存。改动出问题时,拿快照一对比,立刻能看出哪里被改坏了。这个操作不花时间,但真到了凌晨两点排查故障的时候,它就是最踏实的后手。

如果把这套方案从头再搭一次,我自己会把重点放在这几件事上:先确认DR模式下RS的ARP抑制参数,再配keepalived的健康检查和VIP漂移,最后在压测阶段看网卡软中断和连接表增长情况。核心转发逻辑本身不复杂,真正决定集群稳不稳的,往往是这些藏在转发之外的细节。希望这篇详细拆解,能让你少走点弯路,也少熬几个夜。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 3:39:52

计算机系统考点梳理:补码、栈帧与缓存刷题技巧

大二上那门计算机系统课,我是被“做题家”三个字摁着脑袋走完的。一开始觉得这只是自嘲,好像谁刷题多、背得熟,谁就是不懂原理的考试机器;等真到了期末复习,我才发现这门课光靠“看懂”完全不够,你必须在纸…

作者头像 李华
网站建设 2026/10/13 3:39:39

Flutter布局实战:Wrap与Chip组合实现标签动态换行与筛选器

1. 为什么Row和Column撑不住页面布局:Wrap的用武之地1.1 一次标签页溢出事故引发的思考先说我自己的经历。上一篇文章我还在老老实实用Row和Column摆布局,当时正好要做搜索历史标签这个功能,需求很简单:把最近搜过的关键词展示成一…

作者头像 李华
网站建设 2026/10/13 3:38:31

35kV线路三段式电流保护:整定计算到仿真验证全流程详解

做课程设计最怕的不是公式算不完,而是整套流程走下来,原理书翻得滚瓜烂熟,一到仿真和报告环节就原形毕露。尤其35kV线路的三段式电流保护,我见过不少同学第一步就栽在把例题里的系统阻抗直接换成35kV参数,算出来的I段保…

作者头像 李华
网站建设 2026/10/13 3:38:11

Linux进程生命周期:从task_struct、fork到僵尸进程与wait

写后台服务的人&#xff0c;十有八九都碰过“僵尸进程”这个词。你起了一个守护进程&#xff0c;过一会儿发现系统里多了一堆<defunct>状态的子进程&#xff0c;ps 里一片 Z&#xff0c;杀又杀不掉&#xff0c;重启又怕影响正在跑的任务。当时第一反应可能是“这程序有 b…

作者头像 李华
网站建设 2026/10/13 3:38:09

基于Java Socket的多客户端即时通信系统实现与优化

前阵子有个朋友问我&#xff1a;用 Java 写一个多客户端同时通信的小系统&#xff0c;怎么搞&#xff1f;他照着网上的教程写了一个 ServerSocket 的 Demo&#xff0c;单连一切正常&#xff0c;第二个客户端一连上&#xff0c;程序就像被点了穴一样&#xff0c;完全不动。这个问…

作者头像 李华
网站建设 2026/10/13 3:37:54

树洞聊天接入微信生态:从匿名设计到合规落地全复盘

树洞聊天这个品类&#xff0c;核心就一句话&#xff1a;让用户有一个安全的地方&#xff0c;说出不敢对熟人说的话。我一直觉得它天然适合长在微信生态里&#xff0c;用户每天打开微信的次数远超任何独立App&#xff0c;树洞离用户越近&#xff0c;倾诉的门槛就越低。这篇文章把…

作者头像 李华