流量从1万到100万,每次加机器发现瓶颈只是往上挪了一层——负载均衡三层架构的演进实录

45 阅读12分钟

负载均衡与高可用: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),高者成为 Master
  • advert_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 时间内最大失败次数,超限后标记为 down
  • fail_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_reqburst=200 nodelay = 正常 QPS 100,突发允许 200 个排队,超过立即 503

会话保持(Session Stickiness)的场景选择

方案原理适用缺点
ip_hash客户端 IP 哈希固定路由到同一后端中小规模、无 NAT 网关公司出口统一 IP→负载严重不均
sticky cookieNginx Plus 向下游 Set-Cookie,后续请求按 cookie 路由大规模、多级代理Nginx Plus 付费功能
无状态化(推荐)将会话数据外置到 Redis,后端实例完全无状态所有场景需要 Redis 高可用
Token 路由网关在 Header 中植入路由标记API 网关层统一管理增加网关复杂度

首选无状态化——将 Session 数据存入共享 Redis,任一后端实例挂掉后流量切换到其他实例时无状态丢失。ip_hash 是"解决不了无状态化时的妥协方案",不是首选。


三、双活架构与主备架构

维度主备(Active-Standby)双活(Active-Active)
流量分布一台处理全部流量,另一台空闲等待两台(或多台)同时承载流量
故障切换时间几十秒到几分钟(需 DNS 切换或 VIP 漂移)F5 层面秒级(健康检查探测到故障后自动摘除)
资源利用率约 50%(备用节点空闲)接近 100%(全部节点参与服务)
复杂度高(数据同步、缓存一致性、跨机房延迟)

双活架构的四层设计要点

  1. 两机房服务完全等价:应用配置、数据库连接串、消息中间件 Topic、路由地址均保持一致,确保任一机房可独立承接全部流量
  2. F5 按权重分发:各机房初始 50%,健康检查间隔 5-10 秒 + 重试 N 次判定故障后自动摘除该机房
  3. 数据库策略:机房内部读写分离,跨机房主备同步(数据单向复制)
  4. Redis 策略:各机房部署独立实例,不走跨机房主从复制(网络延迟过高导致复制不可靠)。缓存丢失场景通过消息广播触发重同步
  5. 关键工程认知:双活的目标是"一个机房故障后用户无感知切换",而非"两机房数据实时强一致"。前者是可用性设计,后者是一致性设计——两者存在根本性冲突

四、熔断器 Sentinel:防止级联故障

熔断机制的必要性

服务A → 服务B → 服务C
                 └── 服务C 故障,响应超时
服务A 持续调用 BA 的工作线程全部阻塞等待 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 无锁更新当前格子的统计值(调用量、异常量、响应时间)。

三种降级策略

  1. 返回默认值:如"用户会员等级查询"熔断后返回"普通用户",保障核心业务流程不被阻断
  2. 返回缓存数据:返回 Redis 中预存的最近一次成功响应数据作为兜底
  3. 降级为简化逻辑:如"个性化推荐模块"熔断后前端隐藏推荐栏,仅展示通用内容

"熔断器的核心能力 = 快速失败(避免线程阻塞等待)+ 优雅降级(保障核心业务流程可用)+ 半开恢复(探测下游恢复后自动恢复调用)。关键不在'如何熔断',而在'降级后用户体验损失的最小化'——这是技术方案与产品体验的交叉决策点。"


核心要点回顾

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合集