☰
keepalived+LVS高可用架构:VIP漂移与负载均衡实战解析
2026/9/28 8:39:59 网站建设 项目流程

早年做高可用,我吃过不少亏。最典型的一次,是半夜机房交换机抖动,单台入口挂掉,整个业务断了一个多小时,等发现时用户已经骂到第二轮。那之后我才认真把 keepalived+LVS 这套组合吃透,从主备漂移、健康检查到后端真实服务器的 ARP 细节,一点一点调过来。这套方案到今天仍然经典得很:LVS 在内核态做四层分发,keepalived 用 VRRP 保证入口 VIP 不丢,两者像两个齿轮一样咬合,扛住了我后面遇到过的大多数高可用场景。如果你正在给一组业务服务器做入口高可用,或者被 VIP 漂移、负载不均衡、keepalived 配置起不来这类问题卡住,那这篇文章就是按你踩坑的顺序写的,看完照着做就能少走很多弯路。

1. 为什么 keepalived+LVS 是入口高可用的经典组合

1.1 先搞懂两个词:负载均衡和高可用并不是一回事

很多刚接触这套方案的同事,容易把“负载均衡”和“高可用”混在一起,甚至以为 keepalived 就是负载均衡器、LVS 就是高可用软件。其实反过来理解才准确:LVS 做的事情是把进来的流量按调度算法分发给后面的多台真实服务器,它解决的是“如何把流量摊平”;keepalived 做的事情是让两台或者多台 LVS 运行节点共享一个虚拟 IP,正常情况下只有一个节点持有 VIP 在工作,一旦这个节点故障,备用节点立刻接管这个 VIP,它解决的是“入口不能断”。

这两个问题单独拎出来都不复杂,难的是如何组合得让人无感知。keepalived 天然支持 LVS,这是它区别于其他 HA 方案的核心能力。你可以把 keepalived 想象成一个调度员,它管着两件事:一是通过 VRRP 协议在节点之间商量谁是主谁是被,VIP 绑在谁身上;二是实时读取配置文件里的 virtual_server 和 real_server 段落,把 LVS 的转发规则同步进内核的 IPVS 表。后端挂了,keepalived 把对应的 real_server 从转发列表里摘掉;入口节点挂了,整个 VIP 连带着 LVS 规则一起切到备用节点。这一整套联动,不需要写脚本,不用手工去敲 ipvsadm,配置好了它自己会完成。

这套方案的魅力还在于:它发生在内核态,数据包在 IPVS 层就被处理了,不像 Nginx 或者其他用户态代理那样需要把数据从内核复制到用户态再复制回去。所以同样的硬件条件下,LVS 的转发吞吐高得多,特别适合入口流量巨大、连接数极多的场景。我自己搭过的几套里,单台 LVS 承载数万并发连接是非常轻松的,瓶颈往往在后端服务器,而不是在负载均衡层。

1.2 和 Nginx、HAProxy、云负载均衡比,LVS 赢在哪里

我之前也被人问过:现在 Nginx 也能做负载均衡,云上也有现成的 SLB,为什么还要费劲去搞 keepalived+LVS?这个问题的核心在于“你处在架构的哪一层、你的场景到底需要什么”。

先看 Nginx。Nginx 做七层反向代理,能读 HTTP 协议、做 URL 路由、改写请求头,还能做很细的流量控制。但正因为它工作在七层,每一个连接都要在用户态解析 HTTP 协议,转发效率天然低于四层转发。当流量到达几万 QPS、连接数几十万的时候,Nginx 的 CPU 占用会非常夸张,而且你需要额外处理很多连接堆积的问题。LVS 呢,它根本不关心你跑的是 HTTP 还是 TCP 或 UDP,它只做基于 IP 和端口的分发。你要做七层下发的业务逻辑,可以放在 LVS 后面的 Nginx 或者业务网关去做,把“入口分发”和“业务处理”两层解耦,互不拖累。

再看 HAProxy。HAProxy 是专业的负载均衡器,性能也不错,但它同样跑在用户态,而且默认是需要自己管理 VIP 漂移的。如果你不配合 keepalived 去处理 VIP,那只能通过 DNS 切换或者其他外部机制,故障转移的时效性没法保证。LVS 配合 keepalived 是内核态转发加上 VRRP 自动漂移的组合,故障感知和 VIP 切换都在可控的秒级甚至毫秒级范围内,配合健康检查,比依赖 DNS 或者手动干预要可靠得多。

至于云负载均衡,省心是真的省心,点一点就创建好了,还自带 DDoS 清洗之类的能力。但很多内部环境、机房自建场景、混合云边界,还是需要自己能完全掌控入口的转发逻辑。特别是做 Kubernetes 集群的 ingress 入口、数据库读写分离入口、RPC 服务入口时,LVS 的四层转发能力让你能把各种后端服务统一框在一个 VIP 下面,后端再怎么扩容缩容,客户端只需要认准那个 VIP 即可,不用改配置。这也是为什么很多系统到现在依然选择 keepalived+LVS,而不是全盘上云 LB。

2. LVS 核心原理:模式、算法和真正影响性能的关键点

2.1 NAT、DR、TUN 三种模式,应该怎么选

LVS 有三种工作模式,分别是 NAT、DR 和 TUN。很多人选型的时候直接抄别人的 DR 配置,但并不知道为什么别的场景会选 TUN。我建议你把这三种模式的本质搞清楚,不然一旦网络环境变了,你会被回程流量问题折磨到怀疑人生。

先说最容易理解的 NAT 模式。在 NAT 模式下,客户端请求到达 LVS 后,LVS 会改写数据包的目标 IP,转发给后端的真实服务器。后端处理完的结果,还要再回到 LVS,由 LVS 把源地址改成客户端地址再发回去。这个模式的优点是后端服务器只需要一个私有 IP,网关指向 LVS 就行,拓扑简单。但缺点也很致命:所有响应流量都经过 LVS,LVS 很容易成为瓶颈。我见过有人用 NAT 模式承载视频下载业务,LVS 的 CPU 直接被打满,因为出站流量和入站流量挤在同一条窄路上。NAT 适合流量增长不快、请求响应都很轻的小规模场景,不太适合大流量入口。

再说 DR 模式,也是我最常用、大多数互联网架构里最推荐的模式。在 DR 模式下,LVS 只修改请求数据包的目标 MAC 地址,把数据帧转发给选择好的后端服务器,后端服务器收到包之后直接响应给客户端,根本不需要再绕回 LVS。你想一下,这样一来,入站流量经过 LVS,出站流量直接走后端服务器的网卡,LVS 的负载天然就轻了一半以上,数据吞吐自然上去了。但 DR 模式有个硬性要求:LVS 和后端真实服务器必须在同一个二层广播域,因为 LVS 是通过改 MAC 地址转发的,跨路由就做不到。另外,后端服务器必须先把 VIP 配置在自己的回环网卡上,并且关闭 ARP 通告,否则整个局域网里的机器都会和 LVS 抢 VIP,那就会引发很严重的 ARP 风暴,后面的故障排查章节我会专门讲。

最后是 TUN 模式。这个模式把原始数据包封装在 IP 隧道里,LVS 可以把流量调度到跨地域、跨机房的服务器上,突破二层网络限制。但带来的问题是所有节点都要支持隧道协议,配置复杂度上了一个台阶,而且隧道本身也有额外的资源开销。我在实际业务中很少用 TUN,除非确实有跨机房负载均衡的需求,否则优先选 DR,退而求其次选 NAT,TUN 只作为特定场景的备选。

这三种模式的取舍可以总结成一张对比表,方便你以后做方案时直接参照:

模式响应是否经过 LVS性能特征网络要求适用场景
NAT是,进出都过 LVS中等,LVS 容易成瓶颈后端私有网段,网关指向 LVS流量小、简单内网、后端数量少
DR否,后端直接回包高,LVS 仅处理入站必须在同一二层网络高并发入口、电商/RPC/网关
TUN否,后端直接回包较高,但隧道有开销支持隧道协议,跨网络跨机房调度、特殊网络拓扑

2.2 调度算法不是随便选,需要看懂 IPVS 的工作逻辑

LVS 里最常用的调度算法就那几个:rr、wrr、lc、wlc、lblc、sh、dh。很多教程只写一句“默认 wrr 就行”,但实际调优的时候,你会发现算法选得不对,后端老有一部分机器闲着,另一部分机器处理不过来了还在往里塞请求。

rr 就是纯轮询,一个个来,简单粗暴,适合每台后端能力完全一样的场景,但这种场景在真实业务里很少,因为机器配置有差异、部署的其他服务占用也有差异。wrr 是给每台服务器配置权重,weight 值越大接收的连接越多,比如 8 核机器 weight 设 3,4 核机器 weight 设 2,这样能粗粒度地按性能分配。不过 wrr 均衡的是“连接数按权重分配的这一刻”,没有考虑当前后端已有的活跃连接数,极端情况下还是会出现负载偏差。

如果你追求更合理的效果,优先考虑 wlc 或者 lblc。wlc 会在新连接进来时,计算每台后端“当前活跃连接数除以权重”,把新的请求交给比值最小的那台机器。这个策略在多数场景下都很靠谱。lblc 则是在 wlc 的基础上增加了“最近最少使用”的逻辑,在长连接应用里表现更稳。如果你做的是属于固定用户固定入口的业务,比如某些 RPC 框架、长连接网关,那 sh 算法更合适,它通过 hash 客户端 IP 的方式,保证同一个客户端 IP 始终分配到同一个后端,可以减少缓存穿透和重连频率。

这里要特别提醒一个容易误解的点:LVS 的调度是针对“新连接”的,而不是针对数据包的。一个连接的多个数据包,在连接建立之后,就按连接表里的固定记录走同一条路径,不会中途跳来跳去。这个特性保证了 TCP 连接不会因为转发路径变化被打断。也正因如此,你才需要关注 keepalived 的 persistence_timeout 参数,它可以让某个客户端的请求在一定时间内持续落到同一台后端,这个值设得太大,流量就会集中到单台机器;设得太小,有些依赖会话的服务又可能被打散。具体设多少,要看业务特性,不是抄一个 60 就完事。

3. keepalived 是怎么把“高可用”落到实处的

3.1 VRRP 协议和 VIP 漂移原理

keepalived 的高可用能力,底层依赖的是 VRRP 协议,全称是虚拟路由冗余协议。你可以把 VRRP 理解成一群节点在抢一个虚拟 IP 的“使用权”,大家通过多播报文通信,优先级高的节点在正常情况下持有 VIP,其余节点处于待命状态。

这些节点之间有一个虚拟路由 ID,在同一个二层网络里,相同 ID 的节点会被划分为同一个 VRRP 组。每个节点都有一个优先级,范围通常是 0 到 255,数字越大越优先。主节点会周期性发送 VRRP 通告报文,告诉其他节点“我还活着,VIP 还是我的”。备用节点如果连续一段时间没收到主节点的通告,就会认为主节点挂了,开始竞选接替者。这里有个大家经常忽略的细节:不是非等主节点彻底死掉才切换,如果主节点的网卡出现了问题导致通告发送不出去,备用节点一样会启动接管,这种情况下合理配置 VRRP 的告警和日志非常重要。

VRRP 这个机制带来的价值是:客户端永远只需要访问那个 VIP,不需要知道背后有几台机器,也不需要感知到任何故障切换。切换的时候,新的主节点会立即在自己的网卡上配置这个 VIP,同时倾泻一段免费 ARP 报文,告诉整个广播域“这个 VIP 的 MAC 地址现在变成我了”。只要这个过程足够短,对客户端来说基本无感。

但这里要提醒一句:VRRP 报文是走 IP 协议号 112 的,很多服务器的防火墙默认策略可能会拦掉多播包。你配置完 keepalived 后,如果发现两个节点都持有 VIP、或者谁也不愿意接管 VIP,第一反应应该是查一下防火墙对多播地址 224.0.0.18 的放行情况。用 systemctl status keepalived 和 tcpdump 抓 vrrp 包,往往一眼就能看出来问题在哪。

3.2 keepalived 与 LVS 规则的联动机制

keepalived 之所以能和 LVS 配合得这么顺,一半功劳要给它自带的 LVS 配置解析能力。你可以在 keepalived.conf 里直接写 virtual_server 和 real_server 段落,keepalived 启动后会将这些配置自动下发到内核的 IPVS 规则表中。你不用再去单独写一个脚本来执行 ipvsadm -a,也不用担心主备切换后规则不同步。

从工作流程上看,主节点上的 keepalived 会把 VIP 绑定到网卡,然后读取配置中的 virtual_server,把对应的 VIP:端口、调度算法、后端真实服务器列表、健康检查参数全部加载进 IPVS 规则。健康检查器会周期性探测 real_server 的存活状态,探测失败的节点会被标记为不可用,keepalived 会自动在 IPVS 表里摘掉它;等探测恢复后,它又会重新加回来。整个过程对线上流量完全透明,不需要重启任何服务。

这里有一个重要场景要注意:当主节点故障、备用节点接管后,备用节点不仅要把 VIP 漂移过来,还得重新把整套 LVS 规则加载一遍。所以你一定要保证主备节点的配置文件是一致的,否则切换过后 LVS 规则缺失,VIP 是飘过去了,但流量到了新主节点却转发不出去。我在生产环境里吃过这个亏:某个版本的配置文件里 virtual_server 段只写在主配置文件里,备份节点的另一个目录下的配置根本没同步,切换之后用户请求全部超时。后来我养成了习惯,凡是改动 keepalived.conf,主备两台机器必须同步校验 MD5,并且在备用节点上跑一遍 keepalived -t 做语法验证。

3.3 健康检查:端口通不代表业务正常

keepalived 的健康检查类型主要有 TCP_CHECK、HTTP_GET、SSL_GET、MISC_CHECK 几类,配合 real_server 使用。很多新手图省事,所有服务只用 TCP_CHECK 检测端口端口号,结果后端应用线程池满了,端口还是能连上,keepalived 误判为正常,请求照样打过来,用户体验就是“连上了但转圈圈”。

TCP_CHECK 的原理是向后端指定的端口发起 TCP 连接,连上了就算健康。它适合检测纯 TCP 服务,比如数据库、Redis、自定义 RPC。如果你的后端是 HTTP 服务,更推荐用 HTTP_GET,它可以指定一个具体的 URL 和期望的状态码,真实地模拟一次业务请求。注意这个检查不是只看 200 就可以,你最好用一个专门用于健康检查的接口或者静态页面,同时这个接口要尽量轻量,避免每次健康检查都把数据库打一遍,造成不必要的压力。

除了以上两种,MISC_CHECK 是最灵活的方案,可以指定一个自定义脚本,脚本返回 0 表示健康,非 0 表示异常。你可以用它检查更多业务状态,比如磁盘空间、日志队列积压量、JVM 链路状态。但要注意脚本执行频率别太频繁,否则会成为另一种形式的隐患。delay_loop 参数可以控制健康检查的间隔,通常设置在 3 到 10 秒之间;nb_get_retry 是失败重试次数,delay_before_retry 是重试前的等待时间。这些值不是越大越好,也不是越小越好,要结合后端服务的实际恢复速度来设置。你要是设置得太激进,后端还在启动,keepalived 已经把 VIP 切换到备用节点了,流量就会出现一阵阵的抖动。

4. 实操配置:从环境准备到完整上线

4.1 环境规划和基础依赖安装

先说一个典型部署拓扑,这样后面配置你才有画面感。我们需要两台 LVS 节点,一台作为主节点,一台作为备用节点,两台之间通过 VRRP 协商 VIP 归属。后面挂至少两台真实的业务服务器,运行同样的服务。为了讲得清楚,我假设一个非常常见的场景:VIP 是 192.168.10.100,两台 LVS 分别是 192.168.10.20 和 192.168.10.21,两台后端真实服务器分别是 192.168.10.11 和 192.168.10.12,统一跑 80 端口 HTTP 服务。

这套环境可以运行在物理机、虚拟机或者云化数据中心的自有网段上,核心要求是四台机器在同一个二层网络内。操作系统我用 Rocky Linux 9 举例,其他基于 RHEL 的发行版操作几乎一样;Ubuntu 上也就是把包管理器命令换成 apt,其余思路一致。

首先确认内核是否已经加载了 IPVS 相关模块。LVS 是所有能力都由内核模块提供的,一般发行版内核都预编译了,但需要显式加载。检查一下 ip_vs 模块是否已经在使用:

lsmod | grep ip_vs

如果什么输出都没有,手动加载:

modprobe ip_vs modprobe ip_vs_rr modprobe ip_vs_wrr modprobe ip_vs_sh

想省事的办法是直接把模块名写入/etc/modules-load.d/ipvs.conf,开机自动加载。随后安装用户态工具 keepalived 和 ipvsadm:

dnf install -y keepalived ipvsadm

ipvsadm 是用来查看和维护内核 IPVS 规则的命令行工具,keepalived 是核心的 HA 组件。这两个包都必须装,系统重启后不要忘了确认 keepalived 服务已经设置开机启动:

systemctl enable --now keepalived systemctl status keepalived

很多人在这一步会遇到 keepalived 启动失败,大概率是配置文件还没写。别急,下一步我们就写。

4.2 主备 keepalived.conf 配置详解

先看主节点 192.168.10.20 的/etc/keepalived/keepalived.conf。这是一个完整可用的配置,段落的含义我拆开讲:

global_defs { router_id LVS_MASTER vrrp_garp_interval 0 vrrp_gna_interval 0 } 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/32 dev eth0 } } virtual_server 192.168.10.100 80 { delay_loop 6 lb_algo wrr lb_kind DR persistence_timeout 60 protocol TCP real_server 192.168.10.11 80 { weight 3 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 connect_port 80 } } real_server 192.168.10.12 80 { weight 3 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 connect_port 80 } } }

global_defs 里的 router_id 只是本机标识,可以理解成这台机器的名字,它会在日志里出现。vrrp_garp_interval 和 vrrp_gna_interval 是为了在状态切换时控制免费 ARP 报文发送的频率,保持默认就好。

vrrp_instance 是整个高可用的核心。state 在这只是“初始状态”,真正决定谁是主的是 priority。主节点 priority 100,备节点可以设成 90 或者 95。interface 必须指定你实际的物理网卡名,不要写错,这个参数一旦错了,VIP 和 VRRP 多播都发不出去。virtual_router_id 在同一网段内要保持一致,我见过两个不相关的 keepalived 组用了同一个 ID 导致互相干扰。advert_int 是 VRRP 通告间隔,单位秒,通常 1 秒就够了,不需要太极端。

authentication 是 VRRP 报文的认证,注意 auth_pass 长度不要超过 8 个字符,也不能有空格,否则配置校验会直接报错。这条在旧版本里不严格,新版 keepalived 卡得非常狠,后面故障排查里我会再提到。

virtual_ipaddress 下面就是 VIP,用一个 32 位掩码绑在 eth0 上。这个写法会由 keepalived 自动帮你添加和删除。

然后是 virtual_server 段落。这里配置了 VIP 和端口,lb_algo 用的是 wrr 加权轮询,lb_kind 是 DR 模式。persistence_timeout 60 表示同一个客户端 IP 在 60 秒内尽量被分配到同一台后端,这个值主要看你有多少种业务需要保持会话。real_server 里写的是后端服务器地址和权重,下面是各自独立的健康检查参数。

再看备节点 192.168.10.21 的配置,它与主节点只有两处不同:router_id 换成 LVS_BACKUP,state 写成 BACKUP,priority 调低。其余内容完全一致:

global_defs { router_id LVS_BACKUP vrrp_garp_interval 0 vrrp_gna_interval 0 } vrrp_instance VI_1 { state BACKUP interface eth0 virtual_router_id 51 priority 90 advert_int 1 authentication { auth_type PASS auth_pass 123456 } virtual_ipaddress { 192.168.10.100/32 dev eth0 } } virtual_server 192.168.10.100 80 { delay_loop 6 lb_algo wrr lb_kind DR persistence_timeout 60 protocol TCP real_server 192.168.10.11 80 { weight 3 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 connect_port 80 } } real_server 192.168.10.12 80 { weight 3 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 connect_port 80 } } }

两台节点的配置都写完后,先跑一遍语法检查:

keepalived -t -f /etc/keepalived/keepalived.conf

没有任何 ERROR 输出再启动服务。我之前有一次改漏一个括号,keepalived 服务启动了但马上退出,就是这个检查帮我定位到第 41 行的。

4.3 真实服务器上的关键设置:VIP 绑定与 ARP 抑制

这部分是 DR 模式最容易翻车的地方。后端真实服务器上不能把 VIP 配置在 eth0 上,否则网卡会频繁对外发送 ARP 报文,宣告“192.168.10.100 的 MAC 是我”,整个交换机的 MAC 表都会乱掉。正确的做法是把 VIP 绑定在回环接口上,并且只作为本地地址,不对外广播。

我一般直接在/etc/sysctl.conf里追加以下内容,然后执行sysctl -p生效:

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 = 2

配置的含义是:本机只回答目标 IP 是本机接口地址的 ARP 请求,并且在发送 ARP 报文时尽量使用能够路由到目标地址的接口 IP。这样后端机器可以正常接收 LVS 转发过来的数据包,同时不会对外抢占 VIP。绑定 VIP 本身可以写成 systemd 服务或者启动脚本,我常直接这样绑:

ip addr add 192.168.10.100/32 dev lo

为了开机自动生效,我会把上述命令写进/etc/rc.local,或者做一个简单的 systemd unit。最简单粗暴但有效的方式是写一个vip.service,ExecStart 里执行 ip addr add,确保在网络服务启动后绑上去。这里想强调的是:这个方法一定要写进你的部署手册里,新增加的后端服务器如果不做这步,建好 IPVS 规则后流量看着像在转发,实际上后端根本不收包,或者收了包回不去,表现是连接超时。

4.4 验证配置和主备切换演练

配置完了,先看主节点的 VIP 是否已经生效:

ip addr show eth0 | grep 192.168.10.100

有这个输出说明主节点已经把 VIP 绑上了。接着查看 LVS 转发规则:

ipvsadm -Ln

你会看到类似于下面的输出:

IP Virtual Server version 1.2.1 Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 192.168.10.100:80 wrr -> 192.168.10.11:80 Route 3 0 0 -> 192.168.10.12:80 Route 3 0 0

看到两条 Route 记录,说明 keepalived 已经把 real_server 加载到 IPVS 表了。此时从客户端访问http://192.168.10.100,正常的情况下请求会被透明地转发给 11 和 12 两台后端。

接下来做一次主备切换演练。把主节点的 keepalived 服务停掉:

systemctl stop keepalived

观察几秒钟,在备节点上执行ip addr show eth0,正常情况下备节点已经自动绑上了 192.168.10.100。再在备节点上执行ipvsadm -Ln,也要能看到相同的后端列表。说明整体切换是完整的,不只是 VIP 飘过去了,LVS 规则也跟着到位了。

很多团队只在发生了故障才第一次验证主备切换,结果往往在最紧张的时候发现配置两边不一致,切换完成但服务不可用。我强烈建议把切换演练纳入常规变更流程,每个月做一次,记录切换时长,观察有没有串包和丢包。这套系统本来设计出来就是为了关键时刻不慌,平时不练,关键时刻一定会出问题。

5. 高可用场景下,后端代码也要跟着改

5.1 别让后端代码破坏掉 LVS 带来的高可用

很多人以为上了 keepalived+LVS,高可用就万事大吉了,但真正的问题往往出在后端代码。LVS 只负责把流量带上门,门里面能不能服务好,是另一码事。如果你后端的应用是单实例的、有本地状态的,那么即使负载均衡层再高可用,业务层面依然存在单点。

最典型的状态问题就是 session 存放在本进程内。比如你用 Java 默认的本地 Session、Python 的内存 Session,或者把临时文件写在本地磁盘上。这种情况下,用户第一次请求落到后端 A,登录信息写在 A 的内存里;第二次请求如果被调度到后端 B,B 里没有这份登录状态,用户就需要重新登录。虽然你可以把 LVS 的 persistence_timeout 设长,让同一来源 IP 尽可能地走同一台后端,但后端 A 一旦宕机,用户再次请求就必然被切到 B,而 A 上保存的 session 已经没了,客户的会话直接断裂。这在高可用架构里是不能接受的。

所以正确的思路是强迫后端代码设计成无状态的。把 Session 外置到 Redis、Memcached 或者数据库中,把临时下载文件放到对象存储或者共享存储,把进程内缓存收敛到分布式缓存。这样一来,任何一台后端在任意时刻宕机,其他后端都能接住它的流量,用户请求并没有绑死在哪台机器上。LVS 在 IPVS 表里摘掉故障节点之后,剩余节点是安全的,系统整体才能称得上高可用。

我还见过一种反模式:后端服务在启动时去申请一些全局锁,或者依赖某台机器上的定时任务做数据清洗。当这台机器被 LVS 标记为可用之后,定时任务只会在它上面跑,万一它挂了,其他后端机器虽然还活着,却没有人继续执行清理工作。这种隐藏单点比代码里的 session 更危险。分布式环境里,定时任务最好设计成幂等且可竞争执行的模式,配一把分布式锁,让任何一台荣耀接管都可以,这样才能跟 LVS 的调度逻辑匹配起来。

5.2 超时、重试和连接池,在四层转发下怎么设置才合理

LVS 是工作在四层的,它对流量的处理方式是“见包转发”。一旦连接建立了,它不会去感知你的应用层是否处理成功,也不会像七层代理那样帮你重试失败的请求。也就是说,如果你的后端在处理请求过程中崩溃,TCP 连接被强拆,客户端感知到的就是一次普通的连接中断。去掉前置七层代理重试的掩护后,业务代码自身就得有能力处理这种中断。

我倾向于在客户端调用 RPC 或 HTTP 服务的时候,设置合理的连接超时和读取超时,并做有限的自动重试。但重试要小心,不是所有接口都应该盲目重试。接口如果原来是幂等的,比如查询、生成订单号、更新状态这类,可以重试一次到两次;如果是不幂等的,比如下单扣款,重试就可能导致重复订单,这种情况下宁可快速失败,给用户返回错误提示,也不要让系统自动在背后反复提交。这个取舍平时被很多人忽视,但真到高可用演练时,你会发现不幂等接口的重试会把故障放大好几倍。

另外,后端服务之间的连接池也要注意。在 LVS 这种四层转发模式下,连接一旦创建,只要不断开,它就一直保持在后端节点上。如果你的服务启动时就创建了一堆长连接,故障切换后这些连接并不会自动切换到新的后端,需要代码里加入连接失效检测和重建逻辑。我们现在常用的做法是:连接池连接空闲超过一定时间就主动 ping 一下保活,发现失效立即剔除并从池里重建。否则切换之后,原本打到故障机器上的连接会一直排队等待超时,极大影响恢复速度。

这里还值得提一下 SQL Server、MySQL 这类数据库的高可用。很多人对数据库连接串里填写了一组地址,然后用 keepalived 做 VIP 漂移,这个思路没错,但数据库驱动或者连接池如果检测不到连接断开了,应用层就会出现“操作报错但不知道去哪重建连接”的尴尬。所以配合数据库高可用方案时,也要同步检查驱动层面的 failover 参数,比如 MySQL 的 connectTimeout、socketTimeout,SQL Server 的 Connection Resiliency 等,不然 VIP 漂移成功了,应用层仍然在一段时间内继续使用废弃连接。

6. 常见问题与排查实录

6.1 keepalived exited with permanent error config 怎么定位

这是很多人在新版 keepalived 上会遇到的一个报错,字面意思就是配置存在永久错误,keepalived 启动失败直接退出。升级到 keepalived 2.x 之后,配置文件的解析严格了很多,以前很多“能用但有点不规范”的写法,现在都会直接被拒绝启动。

我遇到过的常见原因有这么几类:第一类是括号或者大括号没闭合,这是最普遍的,global_defs、vrrp_instance、virtual_server、real_server 这些层级最容易多一个或少一个右括号;第二类是 interface 指定的网卡名在机器上不存在,比如明明只有 ens160,你写了 eth0,启动直接报错;第三类是 authentication 里的 auth_pass 超过 8 个字符或者里有空格;第四类是检测到两个 vrrp_instance 共用同一个 virtual_router_id,而且接口相同,被判定为循环配置;还有一类是 vrrp_sync_group 引用的实例不存在,这种报错经常让人摸不着头脑。

定位起来其实不麻烦。先跑配置文件的语法检查:

keepalived -t -f /etc/keepalived/keepalived.conf

如果语法本身没问题,就去翻日志:

journalctl -u keepalived -n 50 --no-pager

日志里通常会写明出错的行号和原因。比如“vrrp instance VI_1: interface eth0 not found”这种,一看就懂。新版 keepalived 还会告诉你具体卡在哪个 token,不用瞎猜。我的建议是,任何一次配置变更上线之前,都先在备用节点上执行一遍语法校验,不要直接改主节点配置文件然后重启。你永远不知道一台运行了半年的机器上有什么你没见过的旧配置残留。

6.2 后端都配上 VIP 了,为什么 ARP 还会乱

DR 模式下最常见的故障现象是:配置看着完全没问题,VIP 也在线,IPVS 规则也在,后端服务器也能从本机访问自己的服务,但从外部客户端访问 VIP 时,时通时断,甚至完全不通。用 tcpdump 抓包会发现,请求包到达了正常的后端,但响应却迟迟发不到客户端。

这个问题的根源十有八九在 ARP 表。DR 模式要求后端真实服务器也必须配置 VIP,但它必须配置在 lo 回环接口上,同时设置 arp_ignore 和 arp_announce 抑制,这是整个方案里最反直觉的地方。你要是不设置这些参数,后端机器会默认响应所有对 VIP 的 ARP 请求。一旦它这么做了,交换机就会学习到“VIP 这个地址对应多个 MAC”,进而把发往 VIP 的数据帧随机送给其中一台,LVS 的调度机制就完全失效了,流量直接跑到后端机器上,结果后端机器虽然收到了数据包,却没有启动对应的 IPVS 转发,自然就出现连不上或者时断时续。

另外还有一种比较隐蔽的情况:你设置了 sysctl,但是只设置了 all 下的参数,没设置 lo 接口下的参数。LVS 转发过来的数据包目标地址既可能是 VIP,也可能经过路由从 lo 进来,所以 all 和 lo 这两个都必须设置。我见过有人把 arp_announce 写在 eth0 上,lo 漏了,结果还是会在某些内核版本上被优先通告,时需要折腾很久才发现。

如果你已经改了 sysctl 参数还是有问题,可以查局域网内到底谁在响应 VIP 的 ARP 请求:

arping -I eth0 -c 3 192.168.10.100

正常情况应该只看到主 LVS 节点回包。如果看到后端服务器也在回包,赶紧回去检查它的 lo 绑没绑 VIP、sysctl 是否生效。多台设备同时应答,就是教科书式的 ARP 错误配置。

6.3 健康检查没问题,但流量还是打到故障后端

有时候你明明在 keepalived.conf 里配置了 TCP_CHECK,后端服务也主动停掉了,但 ipvsadm -Ln 却依然能看到这条 real_server 处于活跃状态,请求还在不断打过来。遇到这种问题,先检查 TCP_CHECK 里 connect_port 有没有填写。很多人填了 connect_timeout,却漏了 connect_port,这时候 keepalived 会用默认端口探测,而你的服务根本不在默认端口上,就会产生误判。

还有一种情况是健康检查失败了,但 IPVS 表不会立即摘除节点,因为 keepalived 需要等待重试次数的周期结束。如果你设置了 nb_get_retry 3、delay_before_retry 3、delay_loop 6,那么从第一次检查失败到摘除节点,需要经历三次重试、每次间隔 3 秒,再加上下一次检测周期,实际时间会比直觉长不少。所以在生产环境里,我一般把 nb_get_retry 设 2,delay_before_retry 设 1,delay_loop 设 4,这样感知一个应用级故障的延迟大概在 6 到 8 秒左右,既不会太敏感,也不会太久。

如果健康检查头部设置正常,后端服务检查本身也响应,但你用 curl 访问 VIP 时,发现偶尔还会短暂失败,那有可能是主备切换过程中,旧主节点的 LVS 连接跟踪表还在生效。新主节点接管的瞬间,连接表中还没有建立对应的转发条目,早期的一批新连接会直接请求到 VIP,却找不到路径。这时客户端会表现为“连接被拒绝”或者超时,一般持续一两秒。解决方法是让客户端的连接池设置重试,或者把 persist_timeout 稍微调小,减少切换时连接跟踪表的残留记忆长度。连接跟踪表的清理不能太激进,因为已有长连接需要平滑过渡,所以这里并没有一个绝对正确的数字,只看业务对瞬间抖动容忍度的要求。

6.4 负载不均衡的调试技巧

在流量不够大的测试阶段,你可能会发现 ipvsadm 显示的 ActiveConn 差别很大,某台后端始终是 0,另一台已经几百了。这时候不要急着怀疑权重配置,先分清楚是调度算法本身不均衡,还是连接保持期的问题。

如果用了 persistence_timeout,同一个客户端 IP 在保持期内会一直落在同一台后端。测试环境里访问来源往往就一两个 IP,所以看起来完全没调度,这其实是正常现象。你可以用ipvsadm -Lcn查看当前连接表,看这些连接是不是都标记了 persistent 相关的状态。如果确认不是长连接保持导致的问题,再看权重是否合理。weight 是一个相对值,weight 3 和 weight 4 的差别并不是按百分比精确分配的,它只影响调度器在计算时的排序权重,真正的效果要在大流量下才能体现。

大流量下依然不均衡,那你得考虑后端服务器自己有没有抢占了太多 CPU、网络命名空间是否隔离等外部因素。LVS 的调度算法只认连接数,并不认 CPU 负载。你可以临时把调度算法从 wrr 改成 wlc 试试,它能根据当前活跃连接数调整下一笔流量的去向。实测下来,wcl 对后端配置参差不齐的场景有明显改善。这里再补一条经验:不要在生产环境调完参数后直接盯着 ipvsadm 看几秒就下结论,IPVS 的连接老化周期大概有几分钟,等一个完整周期结束再评估调度效果,才能得到可信的数据。

最后再分享一点个人体会

用了这么多年 keepalived+LVS,我最大的感受是这套方案的难点从来不在配置文件本身,而在周边环境的严谨性。一套配置正常的系统,真正让你在深更半夜被叫起来的,往往是某个新加的后端机器忘了在 lo 上绑 VIP,或者是防火墙策略悄悄挡了 VRRP 报文,又或者是主备配置只改了一边导致切换后规则缺失。所以我建议你把“配置同步 + 语法校验 + 月度切换演练”这三件事变成例行公事,而不是等出了问题再做一次。新版本 keepalived 对配置的校验很严格,这其实是好事,宁可让它启动时报错,也不想看见系统运行半路才因为输入错误产生隐患。这套组合给你的控制力,是云负载均衡器怎么都给不了的,前提是你真的愿意花时间把它所有细节都吃透。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询