本文面向寻求构建高可靠、高性能网络入口的资深工程师与架构师。我们将从网络协议栈与操作系统内核的视角出发,深入剖析 LVS (Linux Virtual Server) 的工作原理,并结合 Keepalived 的 VRRP 协议实现,最终构建一个能够抵御单点故障、具备自动故障转移能力的双机热备负载均衡集群。全文将贯穿从理论分析、配置实现到生产环境避坑的完整链路,拒绝浅尝辄止的概念堆砌。
现象与问题背景
在任何有一定规模的线上业务中,单点故障(Single Point of Failure, SPOF)都是架构设计中的天敌。一个典型的演进初期,业务系统可能直接暴露在公网,或通过单一的 Nginx/HAProxy 等反向代理对外提供服务。这种架构的脆弱性显而易见:无论是应用服务器宕机,还是反向代理节点自身故障,都将导致整个服务中断。
为了解决应用服务器的单点问题,我们引入了负载均衡器(Load Balancer)。它将流量分发到后端多个无状态的应用服务器实例上,实现了水平扩展和应用层的高可用。然而,这引入了一个新的问题:负载均衡器本身成为了新的单点。如果这台机器硬件故障、操作系统崩溃或网络中断,整个系统流量的入口就瘫痪了,其破坏性甚至超过单个应用服务器的宕机。
因此,我们的核心诉求演变为:如何为负载均衡层本身构建高可用? 这就要求我们设计一个方案,能够部署至少两个负载均衡节点,一个作为主节点(MASTER)处理流量,另一个作为备用节点(BACKUP)随时待命。当主节点失效时,备用节点必须能自动、快速地接管服务,对客户端而言整个切换过程应该是透明的。这正是 LVS 与 Keepalived 组合要解决的核心问题。
关键原理拆解
在深入架构之前,我们必须回归计算机科学的基础,理解 LVS 和 Keepalived 这两个组件背后所依赖的底层原理。这不仅是知其然,更是知其所以然的关键。
LVS:工作在内核态的高性能流量调度器
LVS (Linux Virtual Server) 是一个集成在 Linux 内核中的IP负载均衡技术。它与 Nginx、HAProxy 等工作在用户态的七层负载均衡器有着本质区别,这也是其高性能的根源。
- 内核态 vs. 用户态: 这是一个核心的操作系统概念。应用程序(如 Nginx)运行在用户态,它们不能直接操作硬件,需要通过系统调用(System Call)陷入内核态,由内核代为执行。每一次网络包的收发,都涉及到数据在内核态网络协议栈缓冲区和用户态应用缓冲区之间的多次拷贝,以及内核态与用户态之间的上下文切换。这些操作在高并发场景下会带来巨大的性能开销。
- LVS 的 Netfilter 挂载点: LVS 的核心模块 IPVS (IP Virtual Server) 直接构建在内核的 Netfilter 框架之上。它在网络包进入协议栈的早期阶段(`PREROUTING` 或 `INPUT` 链)就通过预设的规则,直接修改数据包的目的 IP 地址和/或 MAC 地址,然后将其转发出去。整个过程纯粹在内核态完成,没有任何用户态的参与,避免了不必要的内存拷贝和上下文切换,因此性能极高,可以轻松处理百万级的并发连接。
LVS 提供了三种主要的工作模式,其选择直接影响网络拓扑和性能表现:
- NAT (Network Address Translation) 模式: 这是最简单直观的模式。客户端请求到达 LVS Director,Director 将数据包的目的 IP 地址修改为后端真实服务器(Real Server, RS)的 IP 地址后转发。RS 处理完请求后,响应包必须再次经过 Director,由 Director 将源 IP 地址修改回虚拟 IP (Virtual IP, VIP),再返回给客户端。缺点是 Director 成为了所有进出流量的瓶颈,扩展性受限。
- TUN (Tunnelling) 模式: Director 通过 IP 隧道技术将客户端的请求包封装一层新的 IP 头,发送给 RS。RS 解封装后处理,并将响应包直接返回给客户端。这种模式支持 RS 与 Director 不在同一个物理网段,但 IP 封装会带来额外的性能开销。
- DR (Direct Routing) 模式: 这是生产环境最常用、性能最高的模式。Director 接收到请求包后,不修改 IP 地址,而是将数据包的目标 MAC 地址修改为选定的 RS 的 MAC 地址,然后将包转发到局域网中。由于 IP 地址未变(仍是 VIP),RS 接收到包后可以处理。RS 处理完请求后,由于其响应包的源 IP 是 VIP,目标 IP 是客户端 IP,它可以不经过 Director,直接将响应包通过网关返回给客户端。这使得 Director 只处理入向流量,极大地提升了吞吐能力。
Keepalived:基于 VRRP 的高可用性实现
Keepalived 是一个轻量级的高可用解决方案。其核心功能是基于 VRRP (Virtual Router Redundancy Protocol) 协议实现对 LVS Director 的故障转移。
- VRRP 协议: VRRP 是一种容错协议,它允许多台路由器(或主机)组成一个虚拟路由器组。这个组共享一个虚拟 IP 地址(VIP)。在任何时刻,组内只有一台设备作为 MASTER,实际拥有这个 VIP 并响应对该 IP 的 ARP 请求。其他设备则处于 BACKUP 状态,静默监听 MASTER 发送的 VRRP 广播/多播通告包。
- 故障切换机制: MASTER 会周期性地(默认为 1 秒)发送 VRRP 通告包,以宣告自己的存活。如果 BACKUP 节点在预设的超时时间内(通常是 3 倍通告间隔)没有收到通告包,它就会认为 MASTER 已经失效。此时,BACKUP 节点中优先级最高的一个会立即转变为 MASTER 状态,并对外发送 ARP广播(Gratuitous ARP),声明自己接管了 VIP。这个 ARP 广播会强制更新局域网内所有设备(尤其是上游交换机)的 ARP 缓存,将原本指向旧 MASTER MAC 地址的条目更新为新 MASTER 的 MAC 地址。流量便会无缝地切换到新的 MASTER 节点上。
将 LVS 和 Keepalived 结合,Keepalived 负责监控 LVS Director 的健康状态并管理 VIP 的漂移,而 LVS 则专注于高性能的流量分发。二者在不同的层面协同工作,共同构成了高可用的负载均衡解决方案。
系统架构总览
基于上述原理,我们设计的双机热备 LVS 集群架构如下:
我们将用文字描述一幅清晰的架构图。想象一下这个场景:
- 网络入口: 互联网流量首先到达上游的核心交换机。
- 虚拟 IP (VIP): 我们规划一个公网 IP 作为服务的统一入口,例如 `123.123.123.100`。这个 IP 就是 VIP,它在逻辑上存在,但并不固定绑定在任何一台物理服务器的网卡上。
- LVS Director 集群:
- 两台物理或虚拟机服务器,`LVS-Master` (192.168.1.10) 和 `LVS-Backup` (192.168.1.11)。
- 每台服务器上都安装了 LVS (ipvsadm 工具) 和 Keepalived。
- `LVS-Master` 初始时持有 VIP (`123.123.123.100`),处理所有外部流量。它会持续通过 VRRP 协议向局域网广播自己的 MASTER 状态。
- `LVS-Backup` 处于 BACKUP 状态,它不持有 VIP,但会监听 `LVS-Master` 的 VRRP 心跳。
- 后端真实服务器 (Real Server) 集群:
- 多台应用服务器,例如 `RS1` (192.168.1.21), `RS2` (192.168.1.22), `RS3` (192.168.1.23)。
- 这些服务器上部署着实际的业务应用(如 Web 服务、API 服务等)。
- 它们与 LVS Director 处于同一个二层网络中(这是 DR 模式的要求)。
- 流量路径(正常情况): 客户端请求 `123.123.123.100` -> 交换机根据 ARP 表将数据包发给 `LVS-Master` 的 MAC 地址 -> `LVS-Master` (内核 IPVS) 根据调度算法选择一台 RS (例如 `RS2`),将数据包的目标 MAC 地址改为 `RS2` 的 MAC 地址后发出 -> `RS2` 接收并处理请求 -> `RS2` 将响应包直接通过网关发送给客户端。
- 故障切换路径: `LVS-Master` 宕机 -> `LVS-Backup` 在几个心跳周期后检测到故障 -> `LVS-Backup` 状态切换为 MASTER -> `LVS-Backup` 广播 Gratuitous ARP,声明 VIP (`123.123.123.100`) 现在由它的 MAC 地址承载 -> 交换机更新 ARP 缓存 -> 新的流量被导向 `LVS-Backup` -> 系统恢复服务。
核心模块设计与实现
这里的实现细节是工程师的战场,配置中的每一个参数都可能成为性能瓶颈或潜在的故障点。
Keepalived 核心配置
我们以 `LVS-Master` 节点的 `keepalived.conf` 为例,`LVS-Backup` 的配置只有 `state` 和 `priority` 不同。
# /etc/keepalived/keepalived.conf on LVS-Master
global_defs {
# 标识本节点的路由器 ID,建议使用主机名
router_id LVS_MASTER_NODE
}
# VRRP 脚本,用于更精细的健康检查,例如检查 LVS 服务本身是否正常
vrrp_script chk_lvs {
script "/etc/keepalived/check_lvs.sh" # 这是一个自定义脚本
interval 2 # 每 2 秒执行一次
weight -20 # 如果脚本失败,优先级降低 20
}
# VRRP 实例定义
vrrp_instance VI_1 {
state MASTER # 主节点设置为 MASTER
interface eth0 # VIP 绑定的物理网卡
virtual_router_id 51 # 虚拟路由 ID,主备必须一致
priority 150 # 优先级,MASTER > BACKUP (例如 BACKUP 设置为 100)
advert_int 1 # VRRP 通告间隔,单位秒
nopreempt # (可选但推荐) 防止网络抖动导致主备频繁切换
authentication {
auth_type PASS
auth_pass 1111 # 简单的认证密码,主备必须一致
}
# 定义虚拟 IP (VIP)
virtual_ipaddress {
123.123.123.100/24 dev eth0 label eth0:0
}
# 追踪脚本,将脚本的健康状态与 VRRP 实例绑定
track_script {
chk_lvs
}
}
# LVS 服务定义
virtual_server 123.123.123.100 80 {
delay_loop 6 # 健康检查间隔
lb_algo lc # 调度算法:lc (Least-Connection),更智能
lb_kind DR # 工作模式:DR (Direct Routing)
persistence_timeout 50 # 会话保持时间,对于需要登录状态的 Web 应用很重要
protocol TCP
# 定义真实服务器
real_server 192.168.1.21 80 {
weight 1
TCP_CHECK {
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
real_server 192.168.1.22 80 {
weight 1
TCP_CHECK {
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
}
极客解读:
- `nopreempt`:这个参数非常重要。在默认的抢占模式下,如果原来的 MASTER 恢复了,它会因为优先级更高而立刻从 BACKUP 手中抢回 VIP。在不稳定的网络中,这可能导致 VIP 在两个节点间来回“乒乓”,造成服务瞬断。设置 `nopreempt` 后,只要当前的 MASTER 不死,即使原来的 MASTER 恢复了也不会抢占,增加了系统的稳定性。
- `vrrp_script` 和 `track_script`:这是专业的玩法。只靠 Keepalived 进程的存活来判断节点健康是不够的。万一 LVS 内核模块崩溃或配置错误,Keepalived 进程还在,但 LVS 服务已经失效了。通过 `vrrp_script` 定义一个脚本去检查 `ipvsadm -Ln` 的输出是否符合预期,如果脚本失败,就降低本节点的优先级,从而触发自动切换。这是一种应用级的健康检查。
- `lb_algo lc`:调度算法的选择。`rr` (Round Robin) 是最简单的轮询,但不考虑服务器当前的连接数。`lc` (Least-Connection) 会将新连接分配给当前活动连接数最少的服务器,对于处理时间不一的请求(如文件下载、复杂数据库查询)更为公平和高效。
Real Server 关键配置:解决 ARP 广播问题
在 LVS-DR 模式下,这是一个经典且绕不过去的“坑”。VIP 同时配置在了 LVS Director 和所有的 Real Server 上。当网络中有设备查询 VIP 的 MAC 地址时,如果 RS 和 Director 都响应了 ARP 请求,就会造成 ARP 缓存混乱,流量将无法正确到达 Director。
原理层剖析: ARP (Address Resolution Protocol) 用于将 IP 地址解析为 MAC 地址。在 LVS-DR 模式下,VIP 对外只应该由当前的 LVS MASTER Director 来响应 ARP。Real Server 虽然也在本地的 `lo` 环回接口上配置了 VIP(用于正确处理目的地址是 VIP 的数据包),但绝不能对外宣告自己拥有这个 VIP。
实现层解决方案: 我们需要在所有 Real Server 上修改内核参数,来抑制 `lo` 接口上 VIP 的 ARP 行为。
#!/bin/bash
# Real Server Configuration Script for LVS-DR
VIP=123.123.123.100
# 1. 在 lo 接口上配置 VIP
ifconfig lo:0 $VIP netmask 255.255.255.255 broadcast $VIP up
route add -host $VIP dev lo:0
# 2. 修改内核 ARP 相关参数
# arp_ignore: 控制系统在收到 ARP 请求时如何回应
# 设置为 1: 只有当请求的 IP 地址配置在接收该请求的网卡上时,才回应 ARP 请求
echo "1" > /proc/sys/net/ipv4/conf/all/arp_ignore
echo "1" > /proc/sys/net/ipv4/conf/lo/arp_ignore
# arp_announce: 控制系统在发送 ARP 请求时使用哪个 IP 地址作为源地址
# 设置为 2: 总是使用最适合该网络接口的 IP 地址,避免使用 lo 上的地址向外宣告
echo "2" > /proc/sys/net/ipv4/conf/all/arp_announce
echo "2" > /proc/sys/net/ipv4/conf/lo/arp_announce
echo "Real Server for LVS-DR configured successfully."
极客解读: 这段脚本必须在每台 Real Server 上执行。`arp_ignore=1` 保证了当 `eth0` 收到对 VIP 的 ARP 请求时,内核发现 VIP 是配置在 `lo` 上的,而不是 `eth0`,因此选择忽略该请求。`arp_announce=2` 保证了本机向外通信时,不会莫名其妙地使用 VIP 作为源 IP 去发起 ARP 请求。这两个参数的组合,完美地将 RS 上的 VIP“隐藏”了起来,只对本机内核可见,对外部网络透明。
性能优化与高可用设计(对抗层)
一个基础的 LVS+Keepalived 集群搭建完成后,真正的挑战在于如何应对生产环境中的复杂问题。
对抗“脑裂”(Split-Brain)
问题: 脑裂是高可用集群的噩梦。当两个 Director 节点之间的心跳网络(VRRP 通告)中断,但它们各自都能连接到外部网络时,两个节点都会认为对方已经宕机,从而同时进入 MASTER 状态,抢占 VIP。这会导致 VIP 在两个 MAC 地址间疯狂漂移,网络风暴,服务严重中断。
对抗策略:
- 多路心跳: 不要依赖单一网络路径进行心跳。可以配置双网卡做 bond,或者在 Keepalived 中配置多个 `vrrp_instance` 在不同的网段进行心跳,增加冗余。
- 引入仲裁机制: 增加一个第三方的“裁判”。最简单的方式是让两个节点都去 `ping` 上游的网关地址。在 `vrrp_script` 中增加检查,如果连网关都 `ping` 不通,说明是自身网络问题,应主动降低优先级,即使收不到对方心跳也不抢占 MASTER。
- Fencing 机制: 在极端情况下,可以通过带外管理(如 IPMI)或云厂商 API,由检测到脑裂的一方强制将另一方重启或关机。这是一种破坏性的保护手段,但能确保数据一致性。
精细化健康检查
问题: `TCP_CHECK` 只能验证端口是否在监听,无法判断应用是否真正健康。一个 Java 应用可能因为 OOM 导致无法处理新请求,但其监听的端口依然存在。
对抗策略:
- 使用 `HTTP_GET` 或 `HTTPS_GET`: 对于 Web 服务,可以配置 Keepalived 去请求一个特定的健康检查 URL(如 `/health_check`)。在该 URL 的后端逻辑中,可以实现对数据库连接、缓存连接、关键业务逻辑的全面检查。只有当所有检查通过时,才返回 HTTP 200。
- 使用 `MISC_CHECK`: 这是最灵活的武器。你可以编写一个任意复杂的脚本,脚本的退出状态码决定了健康检查的结果(0 为成功,非 0 为失败)。这个脚本可以去检查消息队列的堆积长度、某个关键守护进程是否存在、文件系统是否只读等等,实现深度定制的健康判断。
调度算法与会话保持的权衡
问题: `persistence_timeout` 保证了同一个客户端的请求在指定时间内会被转发到同一台 Real Server,这对于维持用户登录状态、购物车等场景至关重要。但它的副作用是,如果某个用户(或爬虫)产生了大量请求,会导致负载集中在某一台 RS 上,破坏了负载均衡的初衷。
权衡分析:
- 无状态服务: 对于 API 网关、图片服务器等无状态服务,应果断关闭会话保持(`persistence_timeout 0`),使用 `lc` 或 `wlc` (Weighted Least-Connection) 算法,追求极致的负载均衡。
- 有状态服务: 必须开启会话保持。此时,`persistence_timeout` 的值需要仔细评估,设置一个“刚刚好”的值(例如,略大于应用 session 的超时时间)。同时,后端应用架构应尽可能向无状态演进,例如将 session 信息存储在 Redis 或 Memcached 等外部共享存储中,从而摆脱对 LVS 会话保持的依赖。这是更根本的解决方案。
架构演进与落地路径
一个复杂的架构不是一蹴而就的,而是分阶段演进和落地的。
- 阶段一:单点 LVS-DR 验证。 先部署一台 LVS Director 和几台 Real Server。不要引入 Keepalived。这个阶段的核心目标是跑通 LVS-DR 模式,特别是要彻底解决 Real Server 上的 ARP 问题。确保流量能正确分发和回包。
- 阶段二:LVS + Keepalived 主备(Active-Passive)模式。 在阶段一验证成功的基础上,引入第二台 LVS Director,配置 Keepalived 实现主备模式(MASTER/BACKUP)。这是本文详述的经典架构,适用于绝大多数场景,稳定性和可预测性高。
- 阶段三:LVS + Keepalived 双主(Active-Active)模式。 当流量巨大,单台 LVS Director 的网卡或 CPU 成为瓶颈时,可以考虑双主模式。这种模式下,可以配置两个或多个 VIP,例如 VIP1 由 Director A 作为 MASTER,Director B 作为 BACKUP;VIP2 由 Director B 作为 MASTER,Director A 作为 BACKUP。通过 DNS 轮询等方式将流量分散到不同的 VIP 上,从而实现两台 Director 同时工作,提升整个集群的吞吐上限。这种模式增加了配置和管理的复杂性。
- 阶段四:多层 LVS 集群与全局负载均衡。 在超大规模场景(如大型电商、CDN),可以构建多层 LVS 集群。例如,在数据中心入口部署第一层 LVS 集群,负责按业务类型(如网页、视频、API)将流量分发到不同的第二层 LVS 集群。第二层 LVS 集群再将流量分发给具体的应用服务器。跨地域容灾则需要引入 GSLB (Global Server Load Balancing),通过智能 DNS 解析,将用户流量导向最近或最健康的可用数据中心。
从一个简单的技术组件组合,到支撑复杂业务的健壮架构,其间的演进不仅是技术的叠加,更是对业务规模、成本、运维复杂度进行深度权衡的结果。理解其内核原理,才能在每个决策点上,做出最符合当下场景的正确选择。
延伸阅读与相关资源
-
想系统性规划股票、期货、外汇或数字币等多资产的交易系统建设,可以参考我们的
交易系统整体解决方案。 -
如果你正在评估撮合引擎、风控系统、清结算、账户体系等模块的落地方式,可以浏览
产品与服务
中关于交易系统搭建与定制开发的介绍。 -
需要针对现有架构做评估、重构或从零规划,可以通过
联系我们
和架构顾问沟通细节,获取定制化的技术方案建议。