负载均衡与高可用:LVS + Nginx + Sentinel 三层防护架构
流量从每天 1 万到每天 100 万,你不能只靠"加机器"。LVS 四层转发→Nginx 七层路由→Sentinel 应用层熔断,三层架构一层都不能少——少了哪一层,流量就从哪里把你压垮。
阅读约 14 分钟 | 系列第 13/17 篇
一、四层负载均衡与七层负载均衡
| 维度 | 四层(L4,传输层) | 七层(L7,应用层) |
|---|---|---|
| 工作层级 | TCP/UDP,仅解析 IP 地址与端口号 | HTTP/HTTPS,可解析 URL、Header、Cookie |
| 性能 | 高(不解析应用层数据,纯转发) | 较低(需完整解析 HTTP 协议) |
| 功能 | 简单转发、NAT 地址转换 | 按 URL 路由分发、会话保持、缓存、限流、SSL 卸载 |
| 代表性方案 | F5(硬件)、LVS、Nginx stream 模块 | Nginx http 模块、HAProxy |
| 类比 | 快递总站——根据包裹上的城市标签分拣 | 根据包裹内的物品清单决定处理方式 |
常见负载均衡算法
| 算法 | 原理 | 适用场景 |
|---|---|---|
| 轮询(Round Robin) | 按顺序依次分发 | 后端服务器性能均匀 |
| 加权轮询(Weighted RR) | 按预设权重比例分发 | 后端服务器性能不均 |
| ip_hash | 对客户端 IP 哈希取模,同一 IP 固定分配到同一后端 | 需要 Session 粘滞的场景 |
| 最少连接(Least Connections) | 分发到当前活跃连接数最少的后端 | 长连接场景(如 WebSocket) |
| 最短响应时间 | 分发到平均响应时间最快的后端 | 性能敏感的核心接口 |
健康检查机制
- TCP 检查:验证后端端口是否可连接
- HTTP 检查:返回 HTTP 200 状态码方判定为健康(可配置检查路径、期望状态码、超时阈值)
- 检查间隔 + 超时时间 + 重试次数 = 决定故障发现延迟的三要素
二、LVS + Nginx + 应用:三层负载架构
用户请求
│
▼
┌──────────────────────────┐
│ LVS / F5(四层负载均衡) │ ← 第一层:VIP + 直接转发,仅检查 IP:Port,性能最高
│ 轮询/最小连接 │ F5 双活:两个机房各分配 50% 权重,故障自动剔除
└──────────────────────────┘
│ │
▼ ▼
┌────────┐ ┌────────┐
│ Nginx │ │ Nginx │ ← 第二层:七层反向代理,按 URL 路径路由至不同应用服务
│ 反向代理│ │ 反向代理│ SSL 卸载、限流、静态资源缓存
└────────┘ └────────┘
│ │
▼ ▼
┌──────┐ ┌──────┐
│ App │ │ App │ ← 第三层:应用实例,无状态化部署
└──────┘ └──────┘
分层设计的依据:L4 性能高但功能弱——仅能根据 IP:Port 转发,不解析应用协议。L7 功能强但性能较低——需完整解析 HTTP 协议后才能执行 URL 路由和 Header 处理。三层架构将 L4 部署于最前端拦截第一波流量(纯转发、低延迟),L7 在第二层按需进行协议级处理(URL 路由、SSL 终止),各层职责明确,运维解耦。
LVS 三种工作模式
| 模式 | 原理 | 优缺点 | 适用 |
|---|---|---|---|
| NAT(网络地址转换) | Director 同时修改请求/响应报文的源目 IP | 简单,但 Director 是瓶颈(所有流量双向经过) | 小规模,Director 性能足够的场景 |
| DR(直接路由) | Director 只改请求帧的目标 MAC,Real Server 直接回客户端 | 性能最高(Director 只处理入站,Real Server 出站直连),但要求 Director 和 Real Server 在同一物理网段 | 高性能场景(最常用) |
| TUN(IP 隧道) | Director 在原始 IP 报文外封装一层 IP 头,Real Server 解封后直接回客户端 | 支持跨网段(Real Server 可分布在不同机房),但 IP 隧道封装有额外开销 | 跨机房部署 |
Keepalived:VRRP 协议与 VIP 漂移
LVS 本身只做负载均衡,不能解决 Director 单点故障。Keepalived 通过 VRRP(虚拟路由器冗余协议)实现高可用:
正常状态:
Director A(Master)—— 持有 VIP 192.168.1.100
Director B(Backup) —— 监听 A 的心跳(组播 224.0.0.18)
故障切换:
A 宕机或心跳超时(默认 3 秒 × 3 次 = 9 秒)
→ B 检测到 Master 消失 → 发送 VRRP 通告(优先级更高)
→ B 接管 VIP → 切换为 Master
→ ARP 广播通知交换机更新 MAC 地址表
恢复后:
A 恢复 → 检测到已有 Master(B)→ 以 Backup 身份加入
(是否抢占取决于 nopreempt 配置)
关键参数:
router_id:VRRP 路由器标识,同一组 Keepalived 必须相同priority:优先级(0-255),高者成为 Masteradvert_int:心跳间隔(秒),默认 1 秒nopreempt:禁止抢占,避免频繁切换
upstream 健康检查
upstream backend {
server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.12:8080 backup; # 仅当其他节点全部不可用时启用
}
max_fails:fail_timeout 时间内最大失败次数,超限后标记为 downfail_timeout:统计窗口 + 故障恢复等待时间backup:热备节点,正常时不参与流量down:手动下线(运维标记)
被动检查 vs 主动检查:upstream 默认使用被动检查(根据请求失败判断),Nginx Plus 支持主动健康检查(定期发送探测请求,在用户流量到达前发现问题)。
Nginx 反向代理完整配置
# /etc/nginx/nginx.conf
upstream app_backend {
# 负载均衡算法(默认轮询)
# least_conn; # 最小连接数
# ip_hash; # IP 哈希(会话保持)
server 192.168.1.10:8080 weight=3 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 weight=2 max_fails=3 fail_timeout=30s;
server 192.168.1.12:8080 backup; # 热备,仅其他节点全部不可用时启用
keepalive 32; # 长连接池大小(减少握手开销)
}
server {
listen 443 ssl http2;
server_name api.example.com;
# SSL 终止——Nginx 解密后以 HTTP 转发到后端
ssl_certificate /etc/nginx/certs/api.example.com.pem;
ssl_certificate_key /etc/nginx/certs/api.example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# 限流——请求速率限制
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;
limit_req zone=api_limit burst=200 nodelay;
# 静态资源——Nginx 直接返回,不穿透到后端
location /static/ {
root /var/www;
expires 30d;
add_header Cache-Control "public, immutable";
}
# API 路由——反向代理到应用集群
location /api/ {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时与重试
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
proxy_next_upstream error timeout http_502 http_503;
proxy_next_upstream_tries 2;
}
}
关键参数解读:
weight:加权轮询的权重——生产环境用于滚动发布时临时降低某台权重keepalive:Nginx 与后端的长连接池——减少 TCP 握手开销(高并发下 QPS 提升 30%+)proxy_next_upstream+proxy_next_upstream_tries:请求失败后自动重试下一台后端——仅在幂等接口(GET/PUT/DELETE)上开启limit_req:burst=200 nodelay= 正常 QPS 100,突发允许 200 个排队,超过立即 503
会话保持(Session Stickiness)的场景选择
| 方案 | 原理 | 适用 | 缺点 |
|---|---|---|---|
ip_hash | 客户端 IP 哈希固定路由到同一后端 | 中小规模、无 NAT 网关 | 公司出口统一 IP→负载严重不均 |
sticky cookie | Nginx Plus 向下游 Set-Cookie,后续请求按 cookie 路由 | 大规模、多级代理 | Nginx Plus 付费功能 |
| 无状态化(推荐) | 将会话数据外置到 Redis,后端实例完全无状态 | 所有场景 | 需要 Redis 高可用 |
| Token 路由 | 网关在 Header 中植入路由标记 | API 网关层统一管理 | 增加网关复杂度 |
首选无状态化——将 Session 数据存入共享 Redis,任一后端实例挂掉后流量切换到其他实例时无状态丢失。
ip_hash是"解决不了无状态化时的妥协方案",不是首选。
三、双活架构与主备架构
| 维度 | 主备(Active-Standby) | 双活(Active-Active) |
|---|---|---|
| 流量分布 | 一台处理全部流量,另一台空闲等待 | 两台(或多台)同时承载流量 |
| 故障切换时间 | 几十秒到几分钟(需 DNS 切换或 VIP 漂移) | F5 层面秒级(健康检查探测到故障后自动摘除) |
| 资源利用率 | 约 50%(备用节点空闲) | 接近 100%(全部节点参与服务) |
| 复杂度 | 低 | 高(数据同步、缓存一致性、跨机房延迟) |
双活架构的四层设计要点
- 两机房服务完全等价:应用配置、数据库连接串、消息中间件 Topic、路由地址均保持一致,确保任一机房可独立承接全部流量
- F5 按权重分发:各机房初始 50%,健康检查间隔 5-10 秒 + 重试 N 次判定故障后自动摘除该机房
- 数据库策略:机房内部读写分离,跨机房主备同步(数据单向复制)
- Redis 策略:各机房部署独立实例,不走跨机房主从复制(网络延迟过高导致复制不可靠)。缓存丢失场景通过消息广播触发重同步
- 关键工程认知:双活的目标是"一个机房故障后用户无感知切换",而非"两机房数据实时强一致"。前者是可用性设计,后者是一致性设计——两者存在根本性冲突
四、熔断器 Sentinel:防止级联故障
熔断机制的必要性
服务A → 服务B → 服务C
└── 服务C 故障,响应超时
服务A 持续调用 B → A 的工作线程全部阻塞等待 B 的响应
→ A 的线程池队列占满 → A 的调用方(网关)也超时
→ 故障以多米诺骨牌效应逐级向上扩散 → 整个系统瘫痪
Sentinel 三级防护体系
| 级别 | 机制 | 判定逻辑 |
|---|---|---|
| 慢调用比例 | 慢调用比例熔断 | 响应耗时超过阈值的请求占比超出设定比例 → 触发熔断 |
| 异常比例/异常数 | 异常比例熔断 | 异常率(或单位时间异常数)超过阈值 → 触发熔断 |
| 流量控制 | QPS 限流 | 每秒请求数超过阈值 → 直接拒绝,不进入调用链 |
核心算法:滑动窗口
固定时间窗口(如 0s-1s 和 1s-2s)在两个窗口边界处存在统计盲区——边界附近的请求可被双倍放行。滑动窗口将时间窗口切分为 N 个格子,窗口向前滑动时丢弃最旧格子的数据、纳入最新格子的数据,统计精度不受窗口边界影响。
滑动窗口示例:假设 1 秒窗口分为 2 个格子(各 500ms),当前时间为 t=1250ms:
格子数组:[G0(0~500ms), G1(500~1000ms), G2(1000~1500ms)]
当前窗口 = G1 + G2(最近 1 秒内的两个格子)
t=1501ms 时:窗口滑动 → 丢弃 G1,纳入 G3(1500~2000ms)
当前窗口 = G2 + G3
每个格子的统计数据在格子的 500ms 时间片内持续累积,窗口滑动时一次性丢弃最旧格子的全部数据。
Sentinel 的滑动窗口实现:默认将 1 秒统计窗口分为 2 个格子(各 500ms),每 500ms 窗口滑动一次。通过 LeapArray 数据结构维护格子数组,CAS 无锁更新当前格子的统计值(调用量、异常量、响应时间)。
三种降级策略
- 返回默认值:如"用户会员等级查询"熔断后返回"普通用户",保障核心业务流程不被阻断
- 返回缓存数据:返回 Redis 中预存的最近一次成功响应数据作为兜底
- 降级为简化逻辑:如"个性化推荐模块"熔断后前端隐藏推荐栏,仅展示通用内容
"熔断器的核心能力 = 快速失败(避免线程阻塞等待)+ 优雅降级(保障核心业务流程可用)+ 半开恢复(探测下游恢复后自动恢复调用)。关键不在'如何熔断',而在'降级后用户体验损失的最小化'——这是技术方案与产品体验的交叉决策点。"
核心要点回顾
L4 与 L7 的职责划分是负载均衡的核心设计考量:L4 工作在 TCP/UDP 层,仅解析 IP 地址和端口号,性能高但功能受限(LVS DR 模式下 Director 仅修改目标 MAC,Real Server 出站直连客户端,性能最高);L7 工作在 HTTP 层,可解析 URL/Header/Cookie,按路径路由分发、SSL 卸载、会话保持,功能强但性能较低。三层架构的分工是 L4(LVS/F5)在最前端以纯转发拦截第一波流量、L7(Nginx/HAProxy)在第二层按需进行协议级处理、应用层实现无状态化水平扩展。双活架构与主备架构的核心差异在于资源利用率和故障切换时间——双活两机房同时承载流量(F5 按权重分发),故障切换在 F5 层面秒级完成,但核心挑战不在流量调度而在数据一致性:数据库跨机房主备同步、Redis 各机房独立部署(不走跨机房主从复制)、跨机房延迟的业务容忍度。
熔断器的核心机制是滑动窗口实时统计请求指标(Sentinel 默认 1 秒窗口切为 2 个 500ms 格子,LeapArray 数据结构维护格子数组,CAS 无锁更新),达到阈值后触发熔断快速失败,通过半开状态探测下游恢复后自动恢复调用。三种降级策略按场景选择:返回默认值(保障核心业务流程不被阻断)、返回缓存数据(Redis 中预存的最近一次成功响应)、降级为简化逻辑(非核心功能隐藏或简化)。核心能力不在"如何熔断"而在"降级后用户体验损失的最小化"。高可用铁律:任何一层禁止出现单点——负载均衡器(VRRP VIP 漂移)、应用服务器(多实例无状态化)、数据库(主从+故障切换)、缓存(哨兵/Cluster)均需冗余部署。
从 LVS 到 Nginx 到 Sentinel,每一层解决的是不同维度的问题——四层包转发、七层内容路由、应用层熔断降级。收藏这张三层架构图,下次扩容时直接对照。
上一篇:《计算机网络基础》 | 下一篇:《容器与K8s核心知识》 系列合集:掘金Java合集