本文旨在为中高级工程师提供一份关于 Keepalived 和 VIP 实现高可用的深度指南。我们将从一个典型的单点故障场景出发,逐步深入探讨其背后的网络协议原理(VRRP、ARP),解构 Keepalived 的核心配置与实现细节,分析脑裂等关键风险的对抗策略,并最终给出一套从简单到复杂的架构演进路径。本文的目标不是一份操作手册,而是一次贯穿网络层、系统层到架构层的实战复盘。
现象与问题背景
在任何不涉及云厂商托管负载均衡器的私有化部署或自建 IDC 环境中,服务的单点故障(Single Point of Failure, SPOF) 是高可用架构首要解决的问题。一个典型的场景是作为流量入口的 Nginx 网关或 LVS 负载均衡器。假设我们部署了一台性能强劲的物理机或虚拟机作为主网关,所有外部流量都通过它进入内部服务集群。一旦这台机器因为硬件故障、操作系统内核崩溃、网络中断或进程异常退出,整个业务系统将瞬间瘫痪,造成不可估量的损失。
面对这一问题,最朴素的想法是部署一台或多台备用服务器。但新的问题随之而来:如何实现流量的自动、快速、透明切换?
- 手动切换:依赖运维人员手动修改 DNS 解析或上层路由配置。这种方式响应时间长(分钟级甚至小时级),且极易引入人为错误,在生产环境中基本不可接受。
- DNS 轮询:配置多条 A 记录指向不同的服务器。这能实现简单的负载均衡,但故障切换能力很弱。DNS 缓存(TTL)的存在使得当一台服务器宕机后,全球各地的 ISP 和客户端仍然会在长达数分钟甚至数小时内继续访问故障 IP,导致大量用户访问失败。
我们需要的是一种能够在 L3/L4 层面,实现秒级甚至亚秒级自动故障切换,且对客户端完全透明的机制。这正是虚拟 IP(Virtual IP Address, VIP) 漂移技术的核心价值所在。Keepalived 正是实现这一机制的业界标准开源组件。
关键原理拆解
要真正理解 Keepalived,我们必须回归到底层的网络通信原理。Keepalived 本身只是一个用户态的守护进程,它巧妙地利用并编排了操作系统内核提供的网络能力和标准的网络协议。
1. 虚拟 IP (VIP) 与 ARP 协议
作为一名架构师,我们首先要明确,IP 地址在内核中并非一定与某个物理网卡(NIC)强绑定。一个网络接口可以配置多个 IP 地址。VIP 本质上就是一个没有绑定到特定物理网卡的、逻辑上的 IP 地址。它可以在集群中的多台服务器之间“漂移”,在任意时刻,只有一台服务器会持有并响应这个 VIP 的流量。
当 VIP 从服务器 A 漂移到服务器 B 时,关键问题是:网络中的其他设备(如交换机、路由器、客户端)如何知道这个 IP 现在对应了一个新的 MAC 地址?这里就引出了网络协议栈的核心——地址解析协议(Address Resolution Protocol, ARP)。
ARP 负责将 IP 地址(L3)解析为 MAC 地址(L2)。网络设备,特别是交换机,内部维护着一张 CAM(Content Addressable Memory)表,记录了 MAC 地址与交换机端口的映射关系。当一台主机(如网关)需要将数据包发送给 VIP 时,它会:
- 查询本地 ARP 缓存,看是否有 VIP 对应的 MAC 地址。
- 如果没有,它会广播一个 ARP 请求:“Who has [VIP]? Tell [My IP]”。
- 当前持有 VIP 的服务器(例如服务器 A)会响应一个 ARP 应答:“[VIP] is at [MAC_A]”。
- 网关收到响应后,更新其 ARP 缓存,并将数据包封装成以太网帧,目标 MAC 地址为 MAC_A,然后发送给交换机。交换机根据 CAM 表将该帧转发到连接服务器 A 的端口。
当故障发生,Keepalived 决定将 VIP 从服务器 A 切换到服务器 B 时,它会执行两个关键的内核操作:首先在服务器 B 的网络接口上添加该 VIP,然后在服务器 A 上移除它。更重要的是,服务器 B 会立即向网络中广播一个免费 ARP(Gratuitous ARP, GARP)包。这个 GARP 包是一个特殊的 ARP 应答,其源 IP 和目标 IP 都是 VIP 自己,源 MAC 则是服务器 B 的 MAC 地址。这个包的作用是“昭告天下”:“现在 VIP 的 MAC 地址是我的(MAC_B)了,请大家更新你们的 ARP 缓存!”。收到 GARP 的交换机和路由器会强制更新它们的 CAM 表和 ARP 缓存,将 VIP 与新的 MAC_B 关联起来。后续所有发往 VIP 的流量就会被正确地转发到服务器 B,从而完成了一次对客户端透明的切换。
2. VRRP 协议 (Virtual Router Redundancy Protocol)
ARP 解决了“如何切换”的问题,但“何时切换”以及“由谁接管”则是由 VRRP 协议决定的。VRRP(RFC 5798)是一个标准化的容错协议,旨在为局域网中的一组路由器(或主机)创建一个虚拟的、高可用的路由器实例。
VRRP 的核心概念包括:
- 虚拟路由器(Virtual Router):由一个 VRID(Virtual Router ID, 0-255)和一组 VIP 地址构成。VRID 在同一局域网内必须唯一。
- VRRP 组(VRRP Group):参与同一个虚拟路由器备份的所有物理设备(如我们的服务器 A 和 B)的集合。
- 角色(State):
- MASTER:VRRP 组中实际持有并响应 VIP 的设备。MASTER 会周期性地通过组播(默认地址 224.0.0.18)发送 VRRP 通告包(Advertisement),宣告自己的存活状态和优先级。
- BACKUP:VRRP 组中的其他设备。它们静默监听 MASTER 的通告包。
- 优先级(Priority):0-255 的整数。优先级越高,越有可能成为 MASTER。通常,我们会给主服务器设置一个较高的优先级(如 150),备用服务器设置一个较低的(如 100)。
- 抢占模式(Preemption):如果一台 BACKUP 服务器发现自己的优先级高于当前 MASTER 的优先级(例如,原先的 MASTER 故障恢复后上线),它是否有权“抢占”MASTER 角色。默认是开启的。
故障切换的逻辑如下:
- 正常情况下,MASTER (服务器 A, priority=150) 按照 `advert_int`(通告间隔,默认 1 秒)发送心跳包。
- BACKUP (服务器 B, priority=100) 持续接收这些心跳包,并重置自己的“MASTER 超时计时器”。这个超时时间通常是 `3 * advert_int + skew_time`。其中 `skew_time = ((256 – priority) * advert_int) / 256`,这是一个偏移量,确保在多个 BACKUP 节点中,优先级最高的那个最先超时。
- 如果服务器 A 宕机,它会停止发送心跳。
- 服务器 B 的超时计时器触发。它确认 MASTER 已失联。
- 服务器 B 将自己的状态从 BACKUP 切换到 MASTER。
- 服务器 B 立即执行上文提到的 VIP 绑定和 GARP 广播操作。
- 切换完成,服务器 B 开始对外服务并发送自己的 VRRP 心跳。
Keepalived 就是 VRRP 协议的一个高质量的开源实现,它作为一个用户态进程,通过与内核的 netlink 接口交互来管理 VIP 地址,并通过原始套接字(raw socket)收发 VRRP 协议包。
系统架构总览
一个典型的基于 Keepalived 的高可用 Nginx 网关架构如下(文字描述):
外部客户端的请求首先到达数据中心的边界路由器或核心交换机。路由器根据路由表将目标地址为 VIP (例如 192.168.1.100) 的流量转发到对应的二层网络(VLAN)。
在这个二层网络中,存在两台或多台物理/虚拟机,我们称之为 Nginx-Node-1 和 Nginx-Node-2。它们拥有各自的物理 IP(如 192.168.1.11 和 192.168.1.12)。
两台节点上都运行着完全相同的 Nginx 服务和 Keepalived 守护进程。它们的 Keepalived 配置属于同一个 VRRP 实例(相同的 `virtual_router_id`)。
- 初始状态:Nginx-Node-1 的 Keepalived 配置了更高的优先级(如 150),成为 MASTER。它绑定了 VIP 192.168.1.100,并周期性发送 VRRP 心跳。Nginx-Node-2 的优先级较低(如 100),作为 BACKUP,静默监听。所有指向 VIP 的流量都由交换机导向 Nginx-Node-1。
- 故障场景:Nginx-Node-1 突然断电。它停止发送 VRRP 心跳。
- 切换过程:Nginx-Node-2 在大约 3 秒后检测到心跳丢失,立即从 BACKUP 状态转为 MASTER。它通过内核接口将 VIP 192.168.1.100 绑定到自己的网卡上,并广播 GARP。
- 恢复状态:交换机更新其 CAM 表,将 VIP 的流量转发到 Nginx-Node-2。客户端的 TCP 连接可能会短暂中断,但对于无状态的 HTTP 请求或配置了重试机制的客户端来说,整个切换过程几乎是无感的。
核心模块设计与实现
理论的深度最终要服务于工程的精度。Keepalived 的威力体现在其简洁而强大的配置文件 `keepalived.conf` 和配套的健康检查机制上。
Keepalived 核心配置
以下是一份生产环境可用的主备 Nginx 节点的 Keepalived 配置样例。
主节点 (Nginx-Node-1) 配置:
global_defs {
# 邮件通知配置,当状态切换时发送邮件
notification_email {
[email protected]
}
notification_email_from [email protected]
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id NGINX_GW_01 # 机器标识,建议唯一
}
# 自定义健康检查脚本
vrrp_script check_nginx {
script "/etc/keepalived/check_nginx.sh" # 脚本路径
interval 2 # 每 2 秒执行一次
weight 50 # 如果脚本成功,优先级+50
fall 2 # 连续2次失败,则认为服务失败
rise 2 # 连续2次成功,则认为服务恢复
}
vrrp_instance VI_1 {
state MASTER # 初始状态为 MASTER
interface ens192 # VRRP 通信和 VIP 绑定的物理网卡
virtual_router_id 51 # VRID, 同一 VRRP 组内必须一致
priority 150 # 优先级,MASTER 节点要高于 BACKUP
advert_int 1 # 心跳间隔,单位秒
authentication {
auth_type PASS # 简单的密码认证
auth_pass mysecret # 认证密码,组内一致
}
virtual_ipaddress {
192.168.1.100/24 dev ens192 label ens192:0 # 要漂移的 VIP
}
track_script {
check_nginx # 引用上面定义的健康检查脚本
}
}
备节点 (Nginx-Node-2) 配置: 绝大部分内容相同,只需修改 `state` 和 `priority`。
vrrp_instance VI_1 {
state BACKUP # 初始状态为 BACKUP
interface ens192
virtual_router_id 51
priority 100 # 优先级低于 MASTER
advert_int 1
# ... 其他部分与 MASTER 相同 ...
virtual_ipaddress {
192.168.1.100/24 dev ens192 label ens192:0
}
track_script {
check_nginx
}
}
极客解读:
- `global_defs`: 全局配置,邮件通知在生产环境中非常重要,它让你第一时间知道发生了主备切换。`router_id` 只是个标识,但最好别冲突。
- `vrrp_script`: 这是 Keepalived 的精髓。VRRP 本身只能检测网络可达性和 Keepalived 进程的死活。但如果 Nginx 进程崩溃或僵死,而服务器网络正常,Keepalived 是无法感知的。`track_script` 解决了这个问题。我们通过一个脚本来检查业务应用的真实健康状况。
- `weight 50`: 这是动态调整优先级的关键。在 MASTER 节点上,初始优先级是 150。如果 `check_nginx.sh` 脚本执行成功,实际生效的优先级是 `150 + 50 = 200`。如果脚本失败,优先级会降为 150。在 BACKUP 节点上,初始为 100,成功后是 150。当 MASTER 的 Nginx 挂掉,它的优先级从 200 降为 150,但仍然高于 BACKUP 的 100。这是个常见的坑!`weight` 应该设计成一旦脚本失败,MASTER 的优先级会低于 BACKUP 的基础优先级。所以,更稳妥的设计是 `weight` 为负数,例如 `weight -60`。这样 MASTER 的 Nginx 挂掉后,优先级变为 `150 – 60 = 90`,低于 BACKUP 的 100,切换就会稳定发生。
- `fall 2 / rise 2`: 防止因为瞬时抖动导致不必要的切换。
- `virtual_router_id`: 在同一个广播域内,这个 ID 绝对不能重复,否则你会看到灾难性的 VIP 漂移冲突。
健康检查脚本实现
`check_nginx.sh` 的实现必须轻量且可靠。一个简单的例子:
#!/bin/bash
# 检查 Nginx 进程是否存在
if [ -z "$(pidof nginx)" ]; then
# 尝试重启 Nginx,如果你的策略是自愈优先
# systemctl restart nginx
# sleep 1
# if [ -z "$(pidof nginx)" ]; then
# exit 1 # 重启失败,脚本退出码为1,触发切换
# fi
exit 1 # 或者直接失败
fi
# 检查 Nginx 端口是否在监听(更可靠)
# netstat -tlnp | grep ':80' | grep 'nginx' > /dev/null
# if [ $? -ne 0 ]; then
# exit 1
# fi
# 检查业务健康检查接口
HTTP_CODE=$(curl -o /dev/null -s -w "%{http_code}" --connect-timeout 1 http://127.0.0.1/health)
if [ "$HTTP_CODE" -ne "200" ]; then
exit 1
fi
exit 0 # 所有检查通过,退出码为0
极客解读:
只检查进程 (`pidof`) 是不够的,进程可能存在但已陷入死锁或无法提供服务。更可靠的方式是结合端口监听检查和业务层面的健康检查接口 (`/health`)。`curl` 的超时设置 (`–connect-timeout`) 至关重要,必须远小于 `track_script` 的 `interval`,以防检查脚本自身阻塞,导致 Keepalived 无法正常调度。
性能优化与高可用设计
看似简单的 Master/Backup 架构,在生产环境中隐藏着诸多风险,其中最致命的就是脑裂(Split-Brain)。
对抗脑裂
脑裂是指在 Master 和 Backup 节点之间的心跳网络发生故障,但它们各自与外部网络的连接都正常。此时,Backup 节点因为收不到 Master 的心跳,会认为 Master 已宕机,于是自行切换为 Master。结果是集群中出现了两个 Master,都持有同一个 VIP。这会导致交换机层面严重的 MAC 地址漂移(MAC Flapping),数据包可能随机发往两个节点,造成服务的大范围中断和数据不一致。
对抗策略:
- 冗余心跳通道:这是最直接有效的方法。在服务器之间建立多条物理链路,例如除了业务网卡,再增加一根直连的网线或者一个专用的心跳 VLAN。Keepalived 自身并不直接支持多心跳接口,但可以在操作系统层面做 a`bonding`(网卡绑定),将多个物理网卡虚拟成一个逻辑网卡,从而实现链路冗余。
- 仲裁机制(Quorum):对于更严肃的场景(如数据库高可用),VRRP 的两节点模型是不够的。真正的集群系统(如 ZooKeeper, etcd, Pacemaker+Corosync)会引入奇数个节点和投票机制。一个节点必须获得超过半数((N/2)+1)的投票才能成为主节点。这从根本上杜绝了脑裂。虽然 Keepalived 本身不提供此功能,但在设计复杂系统时必须考虑这一思想。
- Fencing(隔离):当检测到可能发生脑裂时,采取极端措施将其中一个节点强制下线。例如,通过 IPMI/BMC 接口直接将疑似故障的旧 Master 重启或断电(称为 STONITH – Shoot The Other Node In The Head)。这是保证数据一致性的最后一道防线,在金融交易等场景中是必需的,但 Keepalived 自身不具备此能力,需要外部系统配合。
切换时间分析与优化
preemption
故障切换的总时间 T_failover = T_detection + T_takeover。
- T_detection:由 `advert_int` 决定,理论最小值约为 `3 * advert_int`。将 `advert_int` 从 1 秒降到 0.5 秒或更低可以缩短检测时间,但会增加网络中的组播包数量,带来额外的 CPU 开销。对于大多数 Web 应用,1 秒的 `advert_int`(对应约 3 秒的切换时间)是完全可以接受的。
- T_takeover:包括脚本执行、VIP 绑定和 GARP 广播。这部分时间通常在毫秒级,可以忽略不计。
关于抢占(preemption),`vrrp_instance` 中有一个隐藏的默认配置 `preempt`。这意味着当一个高优先级的节点(如原来的 Master)从故障中恢复后,它会立即抢占 VIP,导致一次服务切换。这在某些场景下会造成不必要的抖动。例如,如果 Master 节点只是因为网络瞬断而重启,恢复后立即抢占,可能会中断 Backup 节点上正在处理的长连接。在这种情况下,可以在 Backup 节点的配置中加入 `nopreempt`,使其在成为 Master 后,即使原 Master 恢复也不会主动让出。这种策略使得集群状态更稳定,但失去了角色固定的可预测性,需要根据业务特性权衡。
架构演进与落地路径
阶段一:基础 Master/Backup (Active/Passive)
这是最简单、最常见的部署模式,适用于绝大多数需要高可用的场景,特别是对于有状态服务(如数据库主库)或资源有限的情况。其优点是逻辑清晰,易于排错。缺点是资源利用率只有 50%,备用服务器在大部分时间处于闲置状态。
阶段二:双 Master (Active/Active)
对于无状态的应用(如 Nginx 网关、API Server),我们可以通过巧妙的配置实现双活,提高资源利用率。方法是使用多个 VIP 和多个 VRRP 实例。
- 定义两个 VIP:VIP1 (192.168.1.100) 和 VIP2 (192.168.1.101)。
- 在 Node-1 上,配置两个 `vrrp_instance`:
- `VI_1` 针对 VIP1,`priority 150` (Master)。
- `VI_2` 针对 VIP2,`priority 100` (Backup)。
- 在 Node-2 上,配置相反:
- `VI_1` 针对 VIP1,`priority 100` (Backup)。
- `VI_2` 针对 VIP2,`priority 150` (Master)。
- 在前端通过 DNS 轮询或上层负载均衡将流量分发到 VIP1 和 VIP2。
这样,正常情况下 Node-1 处理 VIP1 的流量,Node-2 处理 VIP2 的流量,两台服务器都承载了业务。当任意一台宕机,另一台会接管其全部两个 VIP,保证服务不中断。这种模式将资源利用率提升到 100%,但配置和管理复杂度有所增加。
阶段三:扩展到 LVS+Keepalived 集群
当两台网关的性能成为瓶颈时,VRRP 的主备模型就不足以支撑了。我们需要一个真正的负载均衡层。这时,Keepalived 的另一个核心功能——与 LVS (Linux Virtual Server) 的集成——就派上了用场。
架构演进为:
- 前端部署一对 Keepalived 节点,作为 LVS Director 的高可用对。VIP 依然由它们管理。
- 每个 Keepalived 节点上都配置 LVS 的转发规则(IPVS),将到达 VIP 的流量分发到后端的多个 Real Server(例如,一个由 10 台 Nginx 组成的集群)。
- Keepalived 会负责监控后端 Real Server 的健康状况,并动态地从 LVS 的转发池中添加或移除故障节点。
这个架构将高可用(由 Keepalived 的 VRRP 功能保证)和高性能负载均衡(由 LVS 保证)完美结合,是构建大规模、高并发、高可用 Web 服务的经典屠龙之技。它至今仍是许多大型互联网公司在自建数据中心场景下的首选技术方案,其稳定性和性能经过了最严苛的生产环境检验。
总结而言,Keepalived + VIP 是一个看似简单但蕴含了深刻网络原理的经典高可用方案。从理解 GARP 的作用,到精细化配置 `track_script` 和 `weight`,再到对脑裂风险的敬畏和规避,每一步都体现了资深工程师对系统稳定性的极致追求。掌握它,不仅仅是学会一个工具,更是对构建健壮系统的一次深度思考。
延伸阅读与相关资源
-
想系统性规划股票、期货、外汇或数字币等多资产的交易系统建设,可以参考我们的
交易系统整体解决方案。 -
如果你正在评估撮合引擎、风控系统、清结算、账户体系等模块的落地方式,可以浏览
产品与服务
中关于交易系统搭建与定制开发的介绍。 -
需要针对现有架构做评估、重构或从零规划,可以通过
联系我们
和架构顾问沟通细节,获取定制化的技术方案建议。