LVS,全称 Linux Virtual Server,我在一线摸爬滚打这么多年,每次线上流量峰值或者大促之前,它就是我最放心的那道闸门。很多刚接触高并发架构的朋友,一上来就奔着 Nginx、网关这类七层组件去研究,确实没错,但如果你的业务规模到了一定程度,你会发现四层的 LVS 才是整个流量入口的定海神针,它解决的问题很纯粹:让海量 TCP/UDP 连接能够被多台后端服务器分摊处理,而且干得极其漂亮、极其稳定。这篇文章我想从底层原理讲到线上实战,把 LVS 负载均衡的核心技术点一块一块拆开揉碎,分享我实际部署和排障过程中的真实经验,希望能帮你彻底搞懂这台“流量分配器”的硬核之处。
1. 为什么四层负载均衡至今仍是高并发架构的定海神针
很多人在设计架构时习惯性地把目光集中在应用层,觉得业务逻辑、服务治理、容器编排才是核心。但说实话,真正经历过流量突刺的人都清楚,接入层如果扛不住,后面再精致的微服务架构都会瞬间被冲垮。LVS 的核心价值就在于它在 Linux 内核态直接实现了负载均衡,处理性能非常强悍,单机并发连接数轻轻松松就能做到百万级别,这是工作在用户态的 Nginx 和高性能网关难以企及的高度。
1.1 应用层负载均衡与传输层负载均衡的本质差异
要理解 LVS 为什么快,先得搞清楚四层和七层的边界。Nginx 这类七层负载均衡,需要完整读取 HTTP 请求头、处理 URL 匹配、维护 keepalive 状态,再决定转发给哪个上游节点。这意味着每一个包都要穿越内核态和用户态的多个缓冲区,还要进行繁重的协程调度和状态维护。而 LVS 工作在 TCP/IP 协议栈的四层,它不关心请求内容,只需根据一个简单的调度算法修改数据包的目标地址或端口,然后重新计算校验和发出去。整个过程基本都在内核空间完成,没有上下文切换、没有应用层协议解析,性能自然会有数量级的差距。
我做过一个很直观的对比压测:同样的裸金属服务器上,部署 Nginx 作为七层入口,极限 QPS 大概能跑到十几万;而切换到 LVS 的 DR 模式后,纯转发场景下的转发能力直接翻了好几倍,CPU 占用还低了不少。这个差距在大流量场景下会非常明显,Nginx 更适合做七层的路由、限流、缓存之类的精细化控制,而流量进去的第一站交给 LVS 这类四层工具,性价比最高。
1.2 LVS高性能的根本原因:从内核态到零拷贝
LVS 性能强悍的根本原因有三个层面。首先是它直接挂接在 Netfilter 框架的钩子点上,数据包进入协议栈不久就能被处理,根本不需要走完整的协议栈流程。其次是它的调度逻辑极其简洁,查看一下连接转发表,选一台后端,改写 MAC 地址或者 IP 地址,同步连接状态,这个动作可以在纳秒级别完成。第三是 DR 模式天然避开了一次额外的数据拷贝,后端服务器直接回包给客户端,相当于负载均衡器只负责“看门”和“引路”,不充当数据的搬运工。
这里要特别说一句,很多人会把 DR 模式和“流量经过负载均衡器”这件事画上等号,这是个常见的误解。LVS 的 DR 模式只承担入站流量调度,应答流量直接由后端服务器返回给客户端,所以它的带宽压力、CPU 压力都比 NAT 模式低很多,这也是它成为生产环境主流选择的原因。
1.3 什么时候该用LVS,什么时候不该用
凡事都有边界。LVS 不是万能的,它不擅长处理基于应用层内容的转发,你没法根据 Cookie 或者 URL 路径来做分流,那是七层网关的活。所以我在实际架构中一般这样划分:最外层用 LVS 做流量入口的健康检查与高可用,紧接着用 Nginx 集群做七层的路由、静态资源缓存和限流,再往后才是业务服务。这种两层接入的组合拳,既能利用 LVS 的极致吞吐能力,又能保留 Nginx 灵活控制业务的优势,是目前业界的标准姿势。
2. 三大工作模式的原理拆解:NAT、TUN、DR
LVS 的三种模式——NAT、TUN、DR——几乎是技术面试必问的题目,也是实际选型时绕不开的分水岭。很多人背了几句话概念,真到配置时却发现各种问题。这一章我直接拆到数据包层面,给大家讲清楚每种模式到底干了什么,以及应用场景上的取舍。
2.1 DR模式数据通路逐帧分析
先说我用得最多的 DR 模式。假设客户端要访问 VIP 192.168.1.100 的 80 端口,LVS 调度器收到的这个请求帧,目标 MAC 是调度器自己的,目标 IP 是 VIP。调度器拿到包以后,根据调度算法挑出一台真实服务器(比如 192.168.1.10),然后做了一个非常关键的操作:它不修改目标 IP,只把数据链路层的目标 MAC 地址改成真实服务器的 MAC 地址,再把包从连接真实服务器的网卡上发出去。
为什么可以这样?因为真实服务器上除了自己的业务 IP,还需要在 loopback 接口上绑定一个 VIP,并且关闭对 VIP 的 ARP 通告。这样真实服务器接收到目标 IP 为 VIP 的包后,会认为自己就是 VIP 的持有者,接收并处理这个请求。处理完毕后的响应包,源 IP 直接就是 VIP,目标 IP 是客户端,它不需要再回到调度器,直接通过网关发给客户端就完事了。这就是 DR 模式高效的精髓。
我在部署时一般会为每一台真实服务器配置一个 VIP 绑定脚本,关键内容是两条 ip 命令加一条内核参数修改,具体放到后文实操部分展开。要特别注意:真实服务器的网卡不能和调度器在同一个子网内的接口上响应 ARP,否则客户端网关或交换机上的 ARP 缓存会被真实服务器抢先更新,流量就乱套了。这是 DR 模式最经典的一个坑。
2.2 NAT模式与TUN模式的适用场景
NAT 模式则完全是另一套逻辑。调度器不仅要修改目标 IP,还要修改源 IP 和端口映射关系,维护一张 NAT 表来记录每个连接对应的真实服务器。入站流量和出站响应都必须经过调度器,所以调度器成了瓶颈,带宽压力很大,但是好处是真实服务器只需要一个内网 IP,拓扑结构非常简单。过去我维护过一个小型 IDC 集群,后端服务器的操作系统和网络配置五花八门,统一走 NAT 模式是最省心的选择,不过规模上来后我很快就迁移到了 DR。
TUN 模式相对冷门,它通过 IPIP 隧道把原始包封装成新的 IP 包转发给真实服务器。优点是真实服务器可以和调度器跨地域部署,穿越大二层网络限制;缺点是带宽有额外损耗,性能一般,而且要求两端内核都支持隧道模块。实际生产环境中我很少见到 TUN 模式的部署,除非网络拓扑确实特殊,比如后端真实服务器分布在不同的物理机房,又没法统一调度。这种情况下,TUN 可能是唯一解法,但一定要评估好封装开销。
2.3 僵硬的选型逻辑:按业务流量类型确定模式
我总结过一套简单直接的选型方法论:如果后端所有真实服务器和调度器在同一个二层网络中,无脑选 DR;如果真实服务器与调度器跨网段,或者你不想折腾 ARP 和 VIP 绑定这些细节,选 NAT,并且做好调度器的带宽扩容;如果网络拓扑极其复杂、跨越多个机房,选 TUN。大多数中小团队,DR 模式都是正确答案,性能最好,架构也最干净。
3. 调度算法怎么选:从轮询到最少连接再到持久性调度
模式定下来以后,下一步是选调度算法。LVS 内核里内置了十多种算法,但真正在线上生产环境常用的其实就几个。很多人觉得轮询最简单,就一直用轮询,结果后端的慢连接把整条链路拖垮了。调度算法的本质是分配策略,它决定了每台后端真实服务器能分到多少请求,以及是否合理。
3.1 静态算法与动态算法的边界
静态算法包括轮询(rr)、加权轮询(wrr)等,它们完全不依赖后端当前的连接数和负载状态,只是机械地按规则分配。动态算法包括最少连接(lc)、加权最少连接(wlc)、基于局部性的最少连接(lblc)、带复制的基于局部性的最少连接(lblcr)等,这些算法会实时参考后端所维护的连接数。核心区别在于是否“知情”。如果后端服务器的处理能力基本一致,且请求处理时间差距不大,加权轮询就已经足够。但如果后端机器性能参差不齐,或者请求的轻重差别很大,动态算法明显更合理。
3.2 加权最少连接算法的调度细节
我生产环境默认用 wlc,也就是加权最少连接,它能同时兼顾服务器权重和当前连接数。实际调度逻辑是这样的:假设两台真实服务器,权重分别 2 和 1,当前活动连接数都是 0,那么新请求来了会优先分给权重高的那一台;当两台的连接数比例发生偏移时,调度器会计算连接数与权重的比值,选择比值最小的服务器。简单理解,它试图让每台后端承担的连接数量接近它们的权重比例。这比单纯看连接数要科学,因为高配机器权重高,能扛更多连接。当然,wlc 也有一点需要注意:它本身并不感知后端 CPU 和内存的使用率,所谓的“负载”完全由连接数代表,在某些长连接或慢业务场景下,连接数高不代表负载高,反而可能因为业务本身阻塞导致误判,这一点要在日常监控中关注。
3.3 有状态业务的调度陷阱与解决思路
如果你做的是 Web 服务、无状态 API,调度算法随便挑问题不大。但如果业务涉及 Session 保持或者需要同一客户端的请求始终落在同一台后端机器上,那么就必须考虑源地址哈希(sh)调度算法,或者在内核配置中开启 LVS 的持久性调度。
我曾经帮一个朋友调过一套网关系统,后端是多台无状态的网关进程,但客户端会先经过一个登录中心完成握手,再建立一个长连接通道。如果没有开启持久性,第一次请求落在 A 机器,第二次请求落到 B 机器,整个握手上下文就丢了,客户端直接被断开。开启 LVS 的持久性调度后,同一源 IP 在超时时间内的所有请求都会绑定到同一台后端,问题迎刃而解。需要注意超时时间的设置:太长会导致后端缩容时连接迁移慢,太短则可能刚刚打开长连接就被切走,一般我会根据业务心跳时间先设 10 分钟,压测时再根据实际断连情况微调。
4. 实操部署:一套高可用LVS集群的完整落地过程
原理讲了一堆,最终还是得落实到命令和配置文件上。这一章我以最常用的 DR 模式加上 keepalived 高可用为例,把整个部署过程从零走一遍,每一步都会说明为什么这么做,方便你直接抄作业。
4.1 基础架构与网络拓扑设计
生产环境我一般会准备两台调度器,一台作为主节点 MASTER,一台作为备节点 BACKUP,两台共享同一个 VIP。后端至少准备两台真实服务器,跑着实际的业务服务。整体架构可以这样规划:
| 角色 | 说明 | 示例IP |
|---|---|---|
| 主调度器 | 运行 keepalived + LVS 配置,承载 VIP | 192.168.1.10 |
| 备调度器 | 运行 keepalived,主节点故障时接管 VIP | 192.168.1.11 |
| VIP | 对外提供服务的虚拟地址 | 192.168.1.100 |
| 后端真实服务器1 | 运行业务服务,绑定 VIP 到 lo 接口 | 192.168.1.20 |
| 后端真实服务器2 | 运行业务服务,绑定 VIP 到 lo 接口 | 192.168.1.21 |
需要所有机器都在同一个 VLAN 内,二层可达,调度器和真实服务器的业务网卡配置在同一网段。我曾经遇到过由于交换机端口隔离策略导致 ARP 报文无法广播,DR 模式调度怎么都不生效的问题,后来排查到是接入交换机配置了端口隔离。如果条件允许,建议先在一台物理交换机的小范围环境里验证,再做全量部署。
4.2 keepalived + ipvsadm 关键配置逐行注释
调度器上第一步,安装 keepalived 和 ipvsadm。系统是 CentOS 系,命令大概是:
yum install -y keepalived ipvsadm然后在主节点写 keepalived 配置文件,它的核心作用是提供高可用和健康检查,配置默认在/etc/keepalived/keepalived.conf。我给一个精简版配置,方便理解关键项:
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 your_passwd } virtual_ipaddress { 192.168.1.100/24 dev eth0 } } virtual_server 192.168.1.100 80 { delay_loop 6 lb_algo wlc lb_kind DR protocol TCP sorry_server 127.0.0.1 8080 real_server 192.168.1.20 80 { weight 1 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 connect_port 80 } } real_server 192.168.1.21 80 { weight 1 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 connect_port 80 } } }主备节点的区别只在于 state 是 MASTER 还是 BACKUP,以及 priority 的大小。虚拟路由 ID 必须保持一致,且在同一组 VLAN 里不能和别的 keepalived 实例冲突,否则脑裂风险直接拉满。TCP_CHECK 是 keepalived 调用 TCP 三次握手探测后端服务存活性,比那种只做 ping 的检查靠谱很多。
配置好之后先启动 keepalived,然后手动添加 LVS 规则,或者让它通过配置文件自动同步。这里有个习惯问题:很多老手喜欢直接写 keepalived 的 virtual_server 段让配置统一管理,我是建议大家统一用配置文件管理,这样重启后规则能自动恢复。手动操作只需要在调试时使用:
ipvsadm -A -t 192.168.1.100:80 -s wlc ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.20:80 -g -w 1 ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.21:80 -g -w 1注意-g代表 DR 模式,如果该参数漏写默认会是 NAT 模式,这会导致调度器直接把目标 IP 改写成后端服务器地址,而后端服务器的默认网关又不指向调度器,回包时直接发给客户端,但是没有正确的源 NAT,客户端收到的源 IP 是后端的地址,又不是 VIP,链接根本建立不起来。这是我见过新手最容易踩的坑。
4.3 内核参数调整:真正影响性能的几个开关
调度器的系统层面,我一般会调几个内核参数,让转发性能更稳。修改/etc/sysctl.conf:
net.ipv4.ip_forward = 1 net.ipv4.vs.conntrack = 1 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_fin_timeout = 30net.ipv4.ip_forward = 1必须开启,否则调度器不会对数据包做路由和转发动作。vs.conntrack这个参数用于控制 LVS 是否配合连接跟踪表,长连接并发非常高的场景建议开启,但也要留心里程碑式的内存占用问题。
真实服务器上则要做三件事,绑定 VIP 到回环口并禁止 ARP 响应。我习惯写一个脚本,在真实服务器启动时执行,内容如下:
ifconfig lo:0 192.168.1.100 netmask 255.255.255.255 broadcast 192.168.1.100 up echo "1" > /proc/sys/net/ipv4/conf/all/arp_ignore echo "1" > /proc/sys/net/ipv4/conf/all/arp_announce echo "2" > /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 绑定在 lo 接口上,掩码写 32 位纯主机地址。这样做的目的在于让真实服务器识别目标 IP 为 VIP 的包,同时又不会在网段里广播 VIP 的 ARP 信息。arp_ignore 设为 1,表示只用自己的主 IP 响应 ARP 请求,不会用 VIP 或 lo 上的地址去响应;arp_announce 设为 2,表示发送 ARP 通告时使用能到达目标的最优本地地址,而不会用 VIP 地址去通告。这两个参数组合在一起,等于告诉真实服务器:你可以接收 VIP 的包,但绝不能对外宣称自己拥有 VIP。我在初期调试时曾经漏掉 arp_ignore 配置,客户端网关频繁把数据包转发给真实服务器而不是调度器,整个集群瞬间瘫痪,排查了很久才定位到这个 ARP 层面的问题。
4.4 验证链路:从ipvsadm统计到抓包对比
部署完成后不要直接接流量,先做一轮链路验证。调度器上执行:
ipvsadm -L -n --stats正常情况下应该能看到已配置的虚拟服务和真实服务器,且统计数据在持续增加。然后再从一台测试客户端反复访问 VIP,观察统计信息里的 ActiveConn 和 InActConn 是否在均衡增长。
如果发现数据异常,最简单的排查方法是抓包。调度器上抓 eth0 的包,观察请求是否到达调度器,是否发出去了;真实服务器上抓 lo 接口的包,观察 VIP 的请求是否成功进入业务进程。两边对照之后,问题出在哪一层基本就有数了。
5. 实测中常见的坑与排查方法论
最后一部分,分享我这些年维护 LVS 集群过程中遇到的典型故障和排查思路。这些经验没有写在任何官方文档里,但踩过一次之后你会发现,它们才是保证系统稳定运行的关键。
5.1 VIP连通但业务全挂:RS侧ARP通告问题
现象:VIP 能 ping 通,但网页访问直接超时。在调度器上看 ipvsadm 统计,连接数在涨,但后端机器的流量就是起不来。问题基本都出在真实服务器的 ARP 配置上。如果真实服务器没有正确关闭对 VIP 的 ARP 通告,交换机上的 ARP 表会把 VIP 对应的 MAC 地址学习成真实服务器的 MAC,客户端发送的请求包就会被交换机直接转发给真实服务器而不是 LVS 调度器。而真实服务器又没有处理外部请求的正确路由路径,整个请求就卡死在这里。
解决思路也很直接:检查真实服务器 lo 接口的绑定和/etc/sysctl.conf配置是否生效,同时清空交换机上的 ARP 动态表项。如果机房交换机有静态 ARP 绑定策略,最好直接手动把 VIP 的 MAC 地址绑死到调度器的 MAC,免得再被其他机器霸占。
5.2 回包路径混乱:非DR模式配置错误的典型症状
还有一种情况很有意思:客户端请求能通,但响应数据时断时续,或者数据被截断。这通常不是 LVS 本身的问题,而是调度器配置成了 NAT 模式,但后端服务器的默认网关没有指向调度器。在 NAT 模式下,后端由于收到的包源 IP 是客户端,处理完直接发出响应,如果默认网关走的是物理出口,根本没有经过调度器的 NAT 表,到达客户端时源 IP 又不是 VIP,客户端根本不会认这个连接。这种故障我见过太多回了,排查时先在调度器上执行ipvsadm -L -n,确认模式是不是DR,再去后端机器上确认路由表,两步就能定位。
5.3 健康检查与脑裂:keepalived的防护姿态
keepalived 不是万能的,它默认的 TCP_CHECK 只能检查端口连通性,检查不了业务线程是否假死。我建议在业务足够核心的情况下,增加自定义脚本检查,比如通过MISC_CHECK调一个探测脚本,模拟一个真实的业务请求,返回预期结果才算节点健康。
脑裂问题则更隐蔽,两台调度器同时持有 VIP,流量被随机投递,造成集群整个不可用。原因多半是 VRRP 广播被网络策略拦截,或者 virtual_router_id 配置冲突。我一般会在每台调度器上加一个脚本,定期检查自己是否还能收到对方的 VRRP 报文,如果连续丢了很多个,且自己已经从 BACKUP 变成了 MASTER,就直接执行一段自定义的隔离动作,比如撤回 VIP。在极端网络抖动下,宁可丢失一小段服务,也不能让两台同时写同一个 VIP,脑裂造成的破坏远大于单点故障。
5.4 性能调优经验:连接复用与超时参数的实用值
最后聊聊调优。很多人以为 LVS 配置完毕就万事大吉,其实它还有很多细粒度参数影响转发表现。调度器的连接超时时间要注意,默认的连接表条目如果长期被 TIME_WAIT 状态的连接占据,会导致新连接无法建立。我一般会把 TCP_TIMEWAIT_LEN 和 tcp_fin_timeout 调小一点点,同时利用内核的 tcp_tw_reuse 加速回收。后端服务器的 backlog 也要同步调大,否则 LVS 转发很快,但后端内核只允许非常少的连接排队,照样会出现 Connection refused。
再有一点容易被忽视的是真实服务器的本地端口范围。在高并发场景下,真实服务器主动向外发起连接时默认端口范围可能只有几千个,极容易耗尽,导致新请求全部失败。务必要在/etc/sysctl.conf中调整net.ipv4.ip_local_port_range到一个更宽的范围,比如1024 65535,这个配置我在很多次复盘 RPS 飙升时发现的隐形瓶颈。
LVS 这套体系,原理不算复杂,但它涉及的模块却不少:内核网络栈、负载均衡算法、ARP 协议、高可用协议栈、健康检查机制,每一环都会成为故障点。我在实际使用中的体会是,越是这种底层组件,越要陪跑足够多的故障演练。把 DR 模式的 ARP 问题、keepalived 的切换时机、后端服务假死的检测手段全部在测试环境过一遍,线上加机器时才会有底气。通过这篇文章,希望你能把 LVS 从“听过名字的组件”变成“真正能帮我扛住突发流量”的工具。最后再分享一个小技巧:每次变更完配置后,记得ipvsadm -S导出规则,保存一份留底,出问题复盘时你会感谢这个习惯。