本文旨在为中高级工程师及架构师提供一份关于 Keepalived+VIP 高可用方案的深度剖析。我们将超越基础配置,深入探讨其背后的网络协议原理、内核行为,并结合一线工程经验,分析“脑裂”等关键问题、TCP 连接状态在故障切换中的影响,以及架构从主备到双活的演进路径。这不是一篇入门教程,而是一次对经典高可用技术的系统性梳理与实战复盘,帮助你构建真正稳固的系统入口层。
现象与问题背景
在任何有状态或需要单点入口的系统中,单点故障(Single Point of Failure, SPOF)都是高可用架构的头号敌人。一个典型的 Web 服务架构通常如下:客户端流量经过 DNS 解析,指向一个负载均衡器(如 Nginx、LVS 或 HAProxy),再由负载均衡器分发到后端的无状态应用服务器集群。在这个模型中,应用服务器可以水平扩展,数据库可以通过主从复制或集群保证高可用,但负载均衡器本身成为了一个明显的 SPOF。一旦这台机器宕机或其上的进程崩溃,整个服务将对外不可用。
最直接的解决方案是为其配备一个备用节点。但这引出了新的问题:
- 如何判断主节点“已死”?
- 主节点宕机后,如何让备用节点“自动”接管流量?
- 这个“接管”过程对客户端来说是否透明?耗时多久?
- 如何防止两个节点都认为自己是主节点,造成服务混乱(即“脑裂”)?
手动切换 IP 或修改 DNS 记录显然无法满足现代系统对 RTO(Recovery Time Objective)的要求。我们需要一个自动化的、可靠的、低延迟的故障切换机制。Keepalived + VIP 正是解决此类问题的经典方案,它工作在 OSI 模型的第三/四层,与上层应用解耦,具有极高的普适性。
关键原理拆解
要真正掌握 Keepalived,我们必须回归到计算机网络的基础原理。它的魔力并非凭空产生,而是对现有网络协议和操作系统机制的精巧组合。作为架构师,理解这些底层细节至关重要。
1. 虚拟 IP(Virtual IP, VIP)与 IP 别名(IP Aliasing)
VIP 本质上是一个并未与任何特定物理网卡(NIC)绑定的逻辑 IP 地址。在 Linux 内核中,这是通过 IP 别名技术实现的。我们可以使用 `ip addr add` 命令为一个物理网卡配置多个 IP 地址。例如,`eth0` 的物理 IP 是 `192.168.1.10`,我们可以为它再绑定一个 VIP `192.168.1.100`。从内核网络栈的角度看,这两个 IP 地址是平等的,它都会响应针对这两个 IP 的网络请求。Keepalived 的核心任务,就是在主备节点之间“移动”这个 VIP 的绑定关系。
2. ARP 协议与 Gratuitous ARP(免费 ARP)
IP 地址是逻辑地址,在以太网中,数据帧的最终寻址依赖于 MAC 地址(物理地址)。ARP(Address Resolution Protocol)协议负责将 IP 地址解析为 MAC 地址。局域网内的设备(如交换机、路由器)会维护一个 ARP 缓存表,记录了 IP 与 MAC 的映射关系。当一个设备需要与 VIP 通信时,它会广播一个 ARP 请求:“谁有 `192.168.1.100`?请告诉我你的 MAC 地址。” 此时,持有该 VIP 的主节点会响应,告知自己的 MAC 地址。这个映射关系会被请求方缓存下来。
故障切换的关键在于 Gratuitous ARP (GARP)。当备份节点接管 VIP 成为新的主节点后,它必须立即向全网广播一个 GARP 包。这个包的特殊之处在于,它是一个没有请求的 ARP 响应,其内容是:“大家好,IP 地址 `192.168.1.100` 现在对应我的 MAC 地址 `XX:XX:XX:XX:XX:YY`”。收到这个 GARP 包的设备,会强制更新自己的 ARP 缓存,将 VIP 的映射关系从旧主节点的 MAC 地址更新为新主节点的 MAC 地址。这样,后续发往 VIP 的数据包就会被交换机正确地转发到新的主节点上,从而实现流量的切换。这是一个纯粹的二层广播行为,速度极快。
3. VRRP 协议(Virtual Router Redundancy Protocol)
VRRP (RFC 3768) 是实现上述自动化切换的“大脑”。它定义了一套主备选举和状态同步的机制。在一个 VRRP 组中,有以下核心概念:
- VRID (Virtual Router ID): 虚拟路由器的唯一标识,范围 0-255。同一 VRRP 组内的节点必须有相同的 VRID。
- 角色 (State): 主要有 `MASTER`(主)、`BACKUP`(备)和 `INITIALIZE`。任何时候,一个 VRRP 组中只应该有一个 `MASTER`。
- 优先级 (Priority): 0-255 的整数,数值越大,优先级越高。选举时,优先级高者成为 `MASTER`。
- 心跳通告 (Advertisement): `MASTER` 节点会周期性地(默认为 1 秒)向一个特定的组播地址(`224.0.0.18`)发送 VRRP 心跳包,宣告自己的存活。这个包里包含了 VRID 和优先级信息。
工作流程如下:
- 启动时,所有节点进入 `BACKUP` 状态。
- 如果在一定时间内(`Master_Down_Interval`,通常是 3 倍的心跳间隔)没有收到来自 `MASTER` 的心跳,`BACKUP` 节点们会根据优先级开始选举。
- 优先级最高的 `BACKUP` 节点转变为 `MASTER` 状态。
- 新 `MASTER` 会立即接管 VIP,并广播 GARP 来刷新网络设备的 ARP 缓存。
- 如果一个 `MASTER` 节点发现收到了一个比自己优先级更高的心跳包(例如,原主节点恢复),它会主动放弃 `MASTER` 身份,转变为 `BACKUP`(除非配置了非抢占模式)。
Keepalived 正是 VRRP 协议的一个高质量的开源实现。它通过用户态守护进程,与内核网络栈交互来管理 VIP 和发送 VRRP 报文。
系统架构总览
一个典型的基于 Keepalived 的高可用负载均衡架构如下:
- 两台物理机或虚拟机:我们称之为 LB-MASTER 和 LB-BACKUP。它们的硬件配置和操作系统环境应完全一致。
- 物理 IP (Physical IP, PIP):每台机器都配置一个唯一的、用于管理的物理 IP 地址,例如 `192.168.1.10` (LB-MASTER) 和 `192.168.1.11` (LB-BACKUP)。
- 虚拟 IP (Virtual IP, VIP):一个对外的服务 IP 地址,例如 `192.168.1.100`。所有客户端流量都应该指向这个 VIP。
- Keepalived 服务:在两台机器上都安装并运行 Keepalived。LB-MASTER 的 Keepalived 配置为较高的优先级,LB-BACKUP 配置为较低的优先级。它们通过 VRRP 协议在心跳网络上通信。
- 服务软件 (如 Nginx):在两台机器上都安装并配置完全相同的 Nginx 服务。Nginx 监听在 VIP 地址上,或者监听在 0.0.0.0 上。
在正常情况下,VIP 绑定在 LB-MASTER 上,LB-MASTER 的 Keepalived 进程持续发送 VRRP 心跳。LB-BACKUP 则静默监听。当 LB-MASTER 宕机或其 Keepalived 进程崩溃,LB-BACKUP 在 3 秒后未收到心跳,便会自我提升为 MASTER,执行 `ip addr add` 将 VIP 绑定到自己的网卡上,并发送 GARP。整个切换过程对客户端是透明的,客户端仅会感知到几秒钟的延迟或请求超时。
核心模块设计与实现
理论是枯燥的,让我们深入到配置与代码中,看看一个极客工程师如何将其落地。
1. 基础 Keepalived 配置
这是一个典型的主备配置,直接、犀利,不做多余的修饰。
主节点 (LB-MASTER) 配置: `/etc/keepalived/keepalived.conf`
global_defs {
router_id LVS_DEVEL_MASTER
}
vrrp_instance VI_1 {
state MASTER # 初始状态为 MASTER
interface eth0 # 心跳通告和 VIP 绑定的网卡
virtual_router_id 51 # VRID,主备必须一致
priority 100 # 优先级,MASTER 要高于 BACKUP
advert_int 1 # 心跳间隔,单位秒
nopreempt # (可选) 主节点恢复后不抢占,避免网络抖动
authentication {
auth_type PASS
auth_pass 1111 # 简单的认证密码,主备必须一致
}
virtual_ipaddress {
192.168.1.100/24 dev eth0 label eth0:1
}
}
备节点 (LB-BACKUP) 配置: `/etc/keepalived/keepalived.conf`
global_defs {
router_id LVS_DEVEL_BACKUP
}
vrrp_instance VI_1 {
state BACKUP # 初始状态为 BACKUP
interface eth0
virtual_router_id 51
priority 90 # 优先级,低于 MASTER
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.1.100/24 dev eth0 label eth0:1
}
}
关键点解读:
- `state`: 决定了启动时的初始角色,但最终角色由选举决定。
- `priority`: 选举的核心,谁高谁是主。
- `virtual_router_id`: 组的身份标识。
- `nopreempt`: 这是一个非常重要的工程参数。默认情况下,如果原主节点恢复,它会因为优先级更高而立即从备节点手中“抢”回 VIP,这会导致一次不必要的流量切换。配置 `nopreempt` 在备节点上(在某些版本中主节点也需要配置),可以防止这种抢占行为,只有当备节点自己出问题时,主节点才会接管。这在维护窗口或网络抖动场景下非常有用。
2. 应用级健康检查
Keepalived 自身存活不代表业务可用。如果 Keepalived 进程正常,但 Nginx 进程挂了怎么办?流量切换过来依然是 502。我们需要一种机制,让 Keepalived 能够感知业务应用的健康状态。
`vrrp_script` 和 `track_script` 就是为此而生。
# 在 global_defs 或 vrrp_instance 外部定义检查脚本
vrrp_script chk_nginx {
script "/etc/keepalived/check_nginx.sh" # 执行的脚本路径
interval 2 # 每 2 秒检查一次
weight -20 # 如果脚本失败(退出码非0),优先级减 20
fall 2 # 连续失败 2 次才认为真的失败
rise 2 # 连续成功 2 次才认为恢复
}
vrrp_instance VI_1 {
# ... 其他配置 ...
priority 100
track_script {
chk_nginx # 追踪上面定义的脚本
}
}
检查脚本 `/etc/keepalived/check_nginx.sh`:
#!/bin/bash
# 检查 Nginx 进程是否存在
if [ $(ps -C nginx --no-header | wc -l) -eq 0 ]; then
# 尝试重启 Nginx
/usr/sbin/nginx
sleep 1
# 再次检查
if [ $(ps -C nginx --no-header | wc -l) -eq 0 ]; then
# 重启失败,脚本退出码为 1,Keepalived 认为检查失败
exit 1
fi
fi
# Nginx 进程存在,脚本退出码为 0,Keepalived 认为检查成功
exit 0
工作机制剖析:
在主节点上,`priority` 初始为 100。如果 `chk_nginx` 脚本连续两次执行失败(退出码非 0),Keepalived 会将该实例的有效优先级降低 20(`weight -20`),变为 80。这个优先级低于备节点的 90,于是备节点会认为自己是网络中优先级最高的节点,从而触发故障切换。当 Nginx 恢复后,脚本连续成功两次,优先级会恢复到 100。这就是通过动态调整优先级来实现应用感知的故障切换,简单而高效。
对抗层:脑裂、延迟与工程权衡
任何分布式系统都有其阴暗面,Keepalived 也不例外。作为架构师,你需要像一个“破坏者”一样思考,预见可能出现的问题。
1. 脑裂 (Split-Brain)
这是 Keepalived 方案中最臭名昭著的问题。当主备节点之间的心跳网络中断(例如,交换机故障、防火墙策略变更),但它们各自与外部网络都还联通时,就会发生脑裂。备节点因为收不到主节点的心跳,会认为主节点已死,于是自己升级为 MASTER 并接管 VIP。此时,网络中存在两个 MASTER,都对外宣告自己拥有 VIP。这会导致 ARP 缓存不断在两个 MAC 地址之间疯狂切换(ARP Flapping),造成严重的网络丢包和服务中断。
缓解与解决方案:
- 冗余心跳网络:最直接的办法是为主备节点之间建立一条独立的、物理隔离的心跳链路,例如用一根网线直连。然后在 Keepalived 中使用 `unicast_peer` 配置,指定通过该链路进行单播心跳,而不是组播。这样,即使主交换机故障,心跳依然通畅。
- 仲裁机制:引入一个第三方仲裁者,例如一个共享存储的锁、一个 Redis key 或者一个 etcd 实例。节点在成为 MASTER 之前,必须先获取到这个锁。这种方法增加了系统的复杂度和外部依赖,通常用于更复杂的集群系统(如数据库集群)。
- Fencing (隔离):当检测到可能发生脑裂时,通过带外管理(如 IPMI、iLO)或云厂商 API,将旧的 MASTER 节点强制重启或关机(Shoot The Other Node In The Head, STONITH),确保只有一个“活口”。这是最彻底的方案,但也最危险,需要极度谨慎的实现。
2. 故障切换时间(Failover Time)分析
切换不是瞬时的。总耗时 = 检测时间 + 切换时间。
- 检测时间: 由 `Master_Down_Interval` 决定,其计算公式为 `(3 * advert_int) + (priority / 256)`。在我们的配置中,`advert_int` 为 1,所以检测时间大约是 3 秒多一点。这是 RTO 的主要组成部分。
- 切换时间: 包括备节点提升为 MASTER、执行 IP 绑定、发送 GARP、网络设备更新 ARP 表。这个过程通常在亚秒级完成。
总的来说,一个标准的 Keepalived 配置,其故障切换时间通常在 3-5 秒。对于绝大多数 Web 应用是完全可以接受的。但对于高频交易等对延迟极度敏感的场景,这个时间就太长了。
3. TCP 连接状态的丢失
这是一个经常被忽略但至关重要的细节。当故障切换发生时,VIP 被迁移到了新的 MASTER 节点上,但之前建立的所有 TCP 连接的状态信息(如序列号、窗口大小、连接状态机)都保留在旧 MASTER 节点的内核内存中。新的 MASTER 对这些连接一无所知。
当客户端发来的一个属于旧连接的 TCP 包(例如一个 ACK 包)到达新 MASTER 时,新 MASTER 的内核发现内存中没有这个连接的任何信息(找不到对应的 socket),会认为这是一个无效的包,并回应一个 `RST` 包。客户端收到 `RST` 后,会中断连接,导致请求失败。因此,所有在切换瞬间的存量 TCP 连接都会被中断,客户端需要进行重连。
对于 HTTP 这类短连接或具备重试机制的应用来说,这通常不是问题。但对于长连接应用(如 WebSocket、数据库连接),影响就比较大。要解决这个问题,需要引入连接同步机制,例如使用 `conntrackd` 在主备之间同步内核的连接跟踪表。但这会显著增加系统的复杂度和性能开销,需要仔细权衡。
架构演进与落地路径
Keepalived+VIP 方案并非一成不变,它可以根据业务发展阶段和对可用性的不同要求进行演进。
阶段一:Active-Passive (主备模式)
这是最经典的模式,即我们上文讨论的架构。一个节点工作(Active),一个节点空闲待命(Passive)。
- 优点:简单、稳定、易于理解和维护。
- 缺点:资源利用率低,备用节点在正常情况下是闲置的(尽管可以用于运行一些离线任务)。
这个阶段是绝大多数系统的起点,用最小的代价解决了入口层的单点问题。
阶段二:Active-Active (双活模式)
当流量增长,单个负载均衡器成为性能瓶颈,或者为了提高资源利用率,可以演进到双活模式。这可以通过配置多个 VRRP 实例和多个 VIP 来实现。
例如,我们有两个节点(Node A, Node B)和两个 VIP(VIP1, VIP2):
- 在 VRRP 实例 1 中,为 VIP1 配置 Node A 的优先级为 100,Node B 为 90。
- 在 VRRP 实例 2 中,为 VIP2 配置 Node B 的优先级为 100,Node A 为 90。
然后通过 DNS 轮询或智能 DNS 将流量同时导向 VIP1 和 VIP2。正常情况下,Node A 处理 VIP1 的流量,Node B 处理 VIP2 的流量,两台机器都在工作。如果 Node A 宕机,Node B 会因为在两个实例中优先级都变高,而同时接管 VIP1 和 VIP2 的流量。反之亦然。
- 优点:提高了资源利用率和系统总吞吐量。
- 缺点:配置和管理更复杂。需要仔细进行容量规划,确保单节点能承载双倍的流量。故障切换时对单节点的冲击更大。
阶段三:超越 Keepalived 的大规模架构
当业务规模达到一定程度,或者对故障切换延迟有更苛刻的要求时(例如金融级应用),Keepalived+VIP 的局限性会显现出来。此时,需要考虑更先进的架构:
- BGP/ECMP 路由层面高可用:由负载均衡器通过 BGP 协议向核心路由器宣告 VIP 的可达性。多台负载均衡器可以同时宣告同一个 VIP,路由器通过 ECMP (Equal-Cost Multi-Path) 将流量分发到所有节点。单个节点故障,BGP 路由会自动收敛,流量被重新分配到其他健康节点。这是现代大型云厂商实现其负载均衡服务的核心技术,切换速度快(秒级甚至亚秒级),且能实现真正的水平扩展。
- 客户端/服务网格高可用:将高可用逻辑从基础设施层上移到应用层。客户端(或 Sidecar Proxy)内置一个可用的服务端点列表,并实现自己的负载均衡和故障切换逻辑。例如,客户端会定期从服务发现组件(如 Consul, Nacos)获取健康的服务实例列表,当请求一个实例失败后,能自动重试下一个。这种模式提供了最大的灵活性,但对客户端或服务框架有侵入性。
总而言之,Keepalived+VIP 是一个久经考验的“老兵”,它以其简单、可靠、与应用解耦的特性,在众多场景下依然是构建高可用入口层的首选方案。作为架构师,不仅要会用它,更要深刻理解其原理、洞悉其边界,并清晰地知道在何时应该选择它,何时应该超越它。
延伸阅读与相关资源
-
想系统性规划股票、期货、外汇或数字币等多资产的交易系统建设,可以参考我们的
交易系统整体解决方案。 -
如果你正在评估撮合引擎、风控系统、清结算、账户体系等模块的落地方式,可以浏览
产品与服务
中关于交易系统搭建与定制开发的介绍。 -
需要针对现有架构做评估、重构或从零规划,可以通过
联系我们
和架构顾问沟通细节,获取定制化的技术方案建议。