资讯详情

资讯详情

keepalived+LVS高可用架构:VIP漂移与负载均衡实战解析

早年做高可用我吃过不少亏。最典型的一次是半夜机房交换机抖动单台入口挂掉整个业务断了一个多小时等发现时用户已经骂到第二轮。那之后我才认真把 keepalivedLVS 这套组合吃透从主备漂移、健康检查到后端真实服务器的 ARP 细节一点一点调过来。这套方案到今天仍然经典得很LVS 在内核态做四层分发keepalived 用 VRRP 保证入口 VIP 不丢两者像两个齿轮一样咬合扛住了我后面遇到过的大多数高可用场景。如果你正在给一组业务服务器做入口高可用或者被 VIP 漂移、负载不均衡、keepalived 配置起不来这类问题卡住那这篇文章就是按你踩坑的顺序写的看完照着做就能少走很多弯路。1. 为什么 keepalivedLVS 是入口高可用的经典组合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为什么还要费劲去搞 keepalivedLVS这个问题的核心在于“你处在架构的哪一层、你的场景到底需要什么”。先看 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 即可不用改配置。这也是为什么很多系统到现在依然选择 keepalivedLVS而不是全盘上云 LB。2. LVS 核心原理模式、算法和真正影响性能的关键点2.1 NAT、DR、TUN 三种模式应该怎么选LVS 有三种工作模式分别是 NAT、DR 和 TUN。很多人选型的时候直接抄别人的 DR 配置但并不知道为什么别的场景会选 TUN。我建议你把这三种模式的本质搞清楚不然一旦网络环境变了你会被回程流量问题折磨到怀疑人生。先说最容易理解的 NAT 模式。在 NAT 模式下客户端请求到达 LVS 后LVS 会改写数据包的目标 IP转发给后端的真实服务器。后端处理完的结果还要再回到 LVS由 LVS 把源地址改成客户端地址再发回去。这个模式的优点是后端服务器只需要一个私有 IP网关指向 LVS 就行拓扑简单。但缺点也很致命所有响应流量都经过 LVSLVS 很容易成为瓶颈。我见过有人用 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退而求其次选 NATTUN 只作为特定场景的备选。这三种模式的取舍可以总结成一张对比表方便你以后做方案时直接参照模式响应是否经过 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 设 34 核机器 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 和 ipvsadmdnf install -y keepalived ipvsadmipvsadm 是用来查看和维护内核 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_BACKUPstate 写成 BACKUPpriority 调低。其余内容完全一致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.serviceExecStart 里执行 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 带来的高可用很多人以为上了 keepalivedLVS高可用就万事大吉了但真正的问题往往出在后端代码。LVS 只负责把流量带上门门里面能不能服务好是另一码事。如果你后端的应用是单实例的、有本地状态的那么即使负载均衡层再高可用业务层面依然存在单点。最典型的状态问题就是 session 存放在本进程内。比如你用 Java 默认的本地 Session、Python 的内存 Session或者把临时文件写在本地磁盘上。这种情况下用户第一次请求落到后端 A登录信息写在 A 的内存里第二次请求如果被调度到后端 BB 里没有这份登录状态用户就需要重新登录。虽然你可以把 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、socketTimeoutSQL 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 设 2delay_before_retry 设 1delay_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 的连接老化周期大概有几分钟等一个完整周期结束再评估调度效果才能得到可信的数据。最后再分享一点个人体会用了这么多年 keepalivedLVS我最大的感受是这套方案的难点从来不在配置文件本身而在周边环境的严谨性。一套配置正常的系统真正让你在深更半夜被叫起来的往往是某个新加的后端机器忘了在 lo 上绑 VIP或者是防火墙策略悄悄挡了 VRRP 报文又或者是主备配置只改了一边导致切换后规则缺失。所以我建议你把“配置同步 语法校验 月度切换演练”这三件事变成例行公事而不是等出了问题再做一次。新版本 keepalived 对配置的校验很严格这其实是好事宁可让它启动时报错也不想看见系统运行半路才因为输入错误产生隐患。这套组合给你的控制力是云负载均衡器怎么都给不了的前提是你真的愿意花时间把它所有细节都吃透。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →